├── .ecrc ├── .gitattributes ├── Cargo.lock ├── Cargo.toml ├── LICENSE.txt ├── README.md └── src ├── bin ├── cleanup.rs └── hacktoberfest.rs └── main.rs /.ecrc: -------------------------------------------------------------------------------- 1 | { 2 | "Exclude": ["target", ".git", "node_modules", "results"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /.github export-ignore 2 | /.gitignore export-ignore 3 | /CONTRIBUTING.md export-ignore 4 | /.markdownlint.json export-ignore 5 | /.editorconfig export-ignore 6 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.14.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "a55f82cfe485775d02112886f4169bde0c5894d75e79ead7eafe7e40a25e45f7" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "0.7.18" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "atty" 31 | version = "0.2.14" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 34 | dependencies = [ 35 | "hermit-abi", 36 | "libc", 37 | "winapi", 38 | ] 39 | 40 | [[package]] 41 | name = "autocfg" 42 | version = "1.0.1" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 45 | 46 | [[package]] 47 | name = "awesome-rust" 48 | version = "0.1.0" 49 | dependencies = [ 50 | "chrono", 51 | "chrono-humanize", 52 | "diffy", 53 | "env_logger", 54 | "failure", 55 | "futures", 56 | "hyper", 57 | "lazy_static", 58 | "log", 59 | "pulldown-cmark", 60 | "regex", 61 | "reqwest", 62 | "scraper", 63 | "serde", 64 | "serde_json", 65 | "serde_yaml", 66 | "tokio", 67 | ] 68 | 69 | [[package]] 70 | name = "backtrace" 71 | version = "0.3.58" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "88fb5a785d6b44fd9d6700935608639af1b8356de1e55d5f7c2740f4faa15d82" 74 | dependencies = [ 75 | "addr2line", 76 | "cc", 77 | "cfg-if", 78 | "libc", 79 | "miniz_oxide", 80 | "object", 81 | "rustc-demangle", 82 | ] 83 | 84 | [[package]] 85 | name = "base64" 86 | version = "0.13.0" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 89 | 90 | [[package]] 91 | name = "bitflags" 92 | version = "1.2.1" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 95 | 96 | [[package]] 97 | name = "bumpalo" 98 | version = "3.12.0" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" 101 | 102 | [[package]] 103 | name = "byteorder" 104 | version = "1.4.3" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 107 | 108 | [[package]] 109 | name = "bytes" 110 | version = "1.0.1" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" 113 | 114 | [[package]] 115 | name = "cc" 116 | version = "1.0.67" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" 119 | 120 | [[package]] 121 | name = "cfg-if" 122 | version = "1.0.0" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 125 | 126 | [[package]] 127 | name = "chrono" 128 | version = "0.4.19" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 131 | dependencies = [ 132 | "libc", 133 | "num-integer", 134 | "num-traits", 135 | "serde", 136 | "time", 137 | "winapi", 138 | ] 139 | 140 | [[package]] 141 | name = "chrono-humanize" 142 | version = "0.2.1" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "2eddc119501d583fd930cb92144e605f44e0252c38dd89d9247fffa1993375cb" 145 | dependencies = [ 146 | "chrono", 147 | ] 148 | 149 | [[package]] 150 | name = "convert_case" 151 | version = "0.4.0" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 154 | 155 | [[package]] 156 | name = "cssparser" 157 | version = "0.27.2" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" 160 | dependencies = [ 161 | "cssparser-macros", 162 | "dtoa-short", 163 | "itoa", 164 | "matches", 165 | "phf", 166 | "proc-macro2", 167 | "quote", 168 | "smallvec", 169 | "syn", 170 | ] 171 | 172 | [[package]] 173 | name = "cssparser-macros" 174 | version = "0.6.0" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e" 177 | dependencies = [ 178 | "quote", 179 | "syn", 180 | ] 181 | 182 | [[package]] 183 | name = "derive_more" 184 | version = "0.99.17" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" 187 | dependencies = [ 188 | "convert_case", 189 | "proc-macro2", 190 | "quote", 191 | "rustc_version", 192 | "syn", 193 | ] 194 | 195 | [[package]] 196 | name = "diffy" 197 | version = "0.3.0" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "e616e59155c92257e84970156f506287853355f58cd4a6eb167385722c32b790" 200 | dependencies = [ 201 | "nu-ansi-term", 202 | ] 203 | 204 | [[package]] 205 | name = "dtoa" 206 | version = "0.4.8" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" 209 | 210 | [[package]] 211 | name = "dtoa-short" 212 | version = "0.3.3" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6" 215 | dependencies = [ 216 | "dtoa", 217 | ] 218 | 219 | [[package]] 220 | name = "ego-tree" 221 | version = "0.6.2" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "3a68a4904193147e0a8dec3314640e6db742afd5f6e634f428a6af230d9b3591" 224 | 225 | [[package]] 226 | name = "encoding_rs" 227 | version = "0.8.28" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" 230 | dependencies = [ 231 | "cfg-if", 232 | ] 233 | 234 | [[package]] 235 | name = "env_logger" 236 | version = "0.8.3" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f" 239 | dependencies = [ 240 | "atty", 241 | "humantime", 242 | "log", 243 | "regex", 244 | "termcolor", 245 | ] 246 | 247 | [[package]] 248 | name = "failure" 249 | version = "0.1.8" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" 252 | dependencies = [ 253 | "backtrace", 254 | "failure_derive", 255 | ] 256 | 257 | [[package]] 258 | name = "failure_derive" 259 | version = "0.1.8" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" 262 | dependencies = [ 263 | "proc-macro2", 264 | "quote", 265 | "syn", 266 | "synstructure", 267 | ] 268 | 269 | [[package]] 270 | name = "fnv" 271 | version = "1.0.7" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 274 | 275 | [[package]] 276 | name = "form_urlencoded" 277 | version = "1.0.1" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 280 | dependencies = [ 281 | "matches", 282 | "percent-encoding", 283 | ] 284 | 285 | [[package]] 286 | name = "futf" 287 | version = "0.1.4" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b" 290 | dependencies = [ 291 | "mac", 292 | "new_debug_unreachable", 293 | ] 294 | 295 | [[package]] 296 | name = "futures" 297 | version = "0.3.14" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "a9d5813545e459ad3ca1bff9915e9ad7f1a47dc6a91b627ce321d5863b7dd253" 300 | dependencies = [ 301 | "futures-channel", 302 | "futures-core", 303 | "futures-executor", 304 | "futures-io", 305 | "futures-sink", 306 | "futures-task", 307 | "futures-util", 308 | ] 309 | 310 | [[package]] 311 | name = "futures-channel" 312 | version = "0.3.14" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25" 315 | dependencies = [ 316 | "futures-core", 317 | "futures-sink", 318 | ] 319 | 320 | [[package]] 321 | name = "futures-core" 322 | version = "0.3.14" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815" 325 | 326 | [[package]] 327 | name = "futures-executor" 328 | version = "0.3.14" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "10f6cb7042eda00f0049b1d2080aa4b93442997ee507eb3828e8bd7577f94c9d" 331 | dependencies = [ 332 | "futures-core", 333 | "futures-task", 334 | "futures-util", 335 | ] 336 | 337 | [[package]] 338 | name = "futures-io" 339 | version = "0.3.14" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "365a1a1fb30ea1c03a830fdb2158f5236833ac81fa0ad12fe35b29cddc35cb04" 342 | 343 | [[package]] 344 | name = "futures-macro" 345 | version = "0.3.14" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "668c6733a182cd7deb4f1de7ba3bf2120823835b3bcfbeacf7d2c4a773c1bb8b" 348 | dependencies = [ 349 | "proc-macro-hack", 350 | "proc-macro2", 351 | "quote", 352 | "syn", 353 | ] 354 | 355 | [[package]] 356 | name = "futures-sink" 357 | version = "0.3.14" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "5c5629433c555de3d82861a7a4e3794a4c40040390907cfbfd7143a92a426c23" 360 | 361 | [[package]] 362 | name = "futures-task" 363 | version = "0.3.14" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc" 366 | 367 | [[package]] 368 | name = "futures-util" 369 | version = "0.3.14" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025" 372 | dependencies = [ 373 | "futures-channel", 374 | "futures-core", 375 | "futures-io", 376 | "futures-macro", 377 | "futures-sink", 378 | "futures-task", 379 | "memchr", 380 | "pin-project-lite", 381 | "pin-utils", 382 | "proc-macro-hack", 383 | "proc-macro-nested", 384 | "slab", 385 | ] 386 | 387 | [[package]] 388 | name = "fxhash" 389 | version = "0.2.1" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 392 | dependencies = [ 393 | "byteorder", 394 | ] 395 | 396 | [[package]] 397 | name = "getopts" 398 | version = "0.2.21" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" 401 | dependencies = [ 402 | "unicode-width", 403 | ] 404 | 405 | [[package]] 406 | name = "getrandom" 407 | version = "0.1.16" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" 410 | dependencies = [ 411 | "cfg-if", 412 | "libc", 413 | "wasi 0.9.0+wasi-snapshot-preview1", 414 | ] 415 | 416 | [[package]] 417 | name = "gimli" 418 | version = "0.23.0" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" 421 | 422 | [[package]] 423 | name = "h2" 424 | version = "0.3.17" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "66b91535aa35fea1523ad1b86cb6b53c28e0ae566ba4a460f4457e936cad7c6f" 427 | dependencies = [ 428 | "bytes", 429 | "fnv", 430 | "futures-core", 431 | "futures-sink", 432 | "futures-util", 433 | "http", 434 | "indexmap", 435 | "slab", 436 | "tokio", 437 | "tokio-util", 438 | "tracing", 439 | ] 440 | 441 | [[package]] 442 | name = "hashbrown" 443 | version = "0.9.1" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" 446 | 447 | [[package]] 448 | name = "hermit-abi" 449 | version = "0.1.18" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" 452 | dependencies = [ 453 | "libc", 454 | ] 455 | 456 | [[package]] 457 | name = "html5ever" 458 | version = "0.25.2" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148" 461 | dependencies = [ 462 | "log", 463 | "mac", 464 | "markup5ever", 465 | "proc-macro2", 466 | "quote", 467 | "syn", 468 | ] 469 | 470 | [[package]] 471 | name = "http" 472 | version = "0.2.4" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11" 475 | dependencies = [ 476 | "bytes", 477 | "fnv", 478 | "itoa", 479 | ] 480 | 481 | [[package]] 482 | name = "http-body" 483 | version = "0.4.1" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "5dfb77c123b4e2f72a2069aeae0b4b4949cc7e966df277813fc16347e7549737" 486 | dependencies = [ 487 | "bytes", 488 | "http", 489 | "pin-project-lite", 490 | ] 491 | 492 | [[package]] 493 | name = "httparse" 494 | version = "1.7.1" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" 497 | 498 | [[package]] 499 | name = "httpdate" 500 | version = "1.0.0" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "05842d0d43232b23ccb7060ecb0f0626922c21f30012e97b767b30afd4a5d4b9" 503 | 504 | [[package]] 505 | name = "humantime" 506 | version = "2.1.0" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 509 | 510 | [[package]] 511 | name = "hyper" 512 | version = "0.14.12" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "13f67199e765030fa08fe0bd581af683f0d5bc04ea09c2b1102012c5fb90e7fd" 515 | dependencies = [ 516 | "bytes", 517 | "futures-channel", 518 | "futures-core", 519 | "futures-util", 520 | "h2", 521 | "http", 522 | "http-body", 523 | "httparse", 524 | "httpdate", 525 | "itoa", 526 | "pin-project-lite", 527 | "socket2", 528 | "tokio", 529 | "tower-service", 530 | "tracing", 531 | "want", 532 | ] 533 | 534 | [[package]] 535 | name = "hyper-rustls" 536 | version = "0.22.1" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" 539 | dependencies = [ 540 | "futures-util", 541 | "hyper", 542 | "log", 543 | "rustls", 544 | "tokio", 545 | "tokio-rustls", 546 | "webpki", 547 | ] 548 | 549 | [[package]] 550 | name = "idna" 551 | version = "0.2.3" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 554 | dependencies = [ 555 | "matches", 556 | "unicode-bidi", 557 | "unicode-normalization", 558 | ] 559 | 560 | [[package]] 561 | name = "indexmap" 562 | version = "1.6.2" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" 565 | dependencies = [ 566 | "autocfg", 567 | "hashbrown", 568 | ] 569 | 570 | [[package]] 571 | name = "instant" 572 | version = "0.1.12" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 575 | dependencies = [ 576 | "cfg-if", 577 | ] 578 | 579 | [[package]] 580 | name = "ipnet" 581 | version = "2.3.0" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" 584 | 585 | [[package]] 586 | name = "itoa" 587 | version = "0.4.7" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" 590 | 591 | [[package]] 592 | name = "js-sys" 593 | version = "0.3.50" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c" 596 | dependencies = [ 597 | "wasm-bindgen", 598 | ] 599 | 600 | [[package]] 601 | name = "lazy_static" 602 | version = "1.4.0" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 605 | 606 | [[package]] 607 | name = "libc" 608 | version = "0.2.119" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" 611 | 612 | [[package]] 613 | name = "linked-hash-map" 614 | version = "0.5.4" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" 617 | 618 | [[package]] 619 | name = "lock_api" 620 | version = "0.4.6" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" 623 | dependencies = [ 624 | "scopeguard", 625 | ] 626 | 627 | [[package]] 628 | name = "log" 629 | version = "0.4.14" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 632 | dependencies = [ 633 | "cfg-if", 634 | ] 635 | 636 | [[package]] 637 | name = "mac" 638 | version = "0.1.1" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" 641 | 642 | [[package]] 643 | name = "markup5ever" 644 | version = "0.10.1" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd" 647 | dependencies = [ 648 | "log", 649 | "phf", 650 | "phf_codegen", 651 | "string_cache", 652 | "string_cache_codegen", 653 | "tendril", 654 | ] 655 | 656 | [[package]] 657 | name = "matches" 658 | version = "0.1.8" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 661 | 662 | [[package]] 663 | name = "memchr" 664 | version = "2.4.0" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" 667 | 668 | [[package]] 669 | name = "mime" 670 | version = "0.3.16" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 673 | 674 | [[package]] 675 | name = "miniz_oxide" 676 | version = "0.4.4" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" 679 | dependencies = [ 680 | "adler", 681 | "autocfg", 682 | ] 683 | 684 | [[package]] 685 | name = "mio" 686 | version = "0.7.11" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956" 689 | dependencies = [ 690 | "libc", 691 | "log", 692 | "miow", 693 | "ntapi", 694 | "winapi", 695 | ] 696 | 697 | [[package]] 698 | name = "miow" 699 | version = "0.3.7" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 702 | dependencies = [ 703 | "winapi", 704 | ] 705 | 706 | [[package]] 707 | name = "new_debug_unreachable" 708 | version = "1.0.4" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" 711 | 712 | [[package]] 713 | name = "nodrop" 714 | version = "0.1.14" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" 717 | 718 | [[package]] 719 | name = "ntapi" 720 | version = "0.3.6" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" 723 | dependencies = [ 724 | "winapi", 725 | ] 726 | 727 | [[package]] 728 | name = "nu-ansi-term" 729 | version = "0.46.0" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 732 | dependencies = [ 733 | "overload", 734 | "winapi", 735 | ] 736 | 737 | [[package]] 738 | name = "num-integer" 739 | version = "0.1.44" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 742 | dependencies = [ 743 | "autocfg", 744 | "num-traits", 745 | ] 746 | 747 | [[package]] 748 | name = "num-traits" 749 | version = "0.2.14" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 752 | dependencies = [ 753 | "autocfg", 754 | ] 755 | 756 | [[package]] 757 | name = "num_cpus" 758 | version = "1.13.0" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 761 | dependencies = [ 762 | "hermit-abi", 763 | "libc", 764 | ] 765 | 766 | [[package]] 767 | name = "object" 768 | version = "0.23.0" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4" 771 | 772 | [[package]] 773 | name = "once_cell" 774 | version = "1.7.2" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" 777 | 778 | [[package]] 779 | name = "overload" 780 | version = "0.1.1" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 783 | 784 | [[package]] 785 | name = "parking_lot" 786 | version = "0.11.2" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" 789 | dependencies = [ 790 | "instant", 791 | "lock_api", 792 | "parking_lot_core", 793 | ] 794 | 795 | [[package]] 796 | name = "parking_lot_core" 797 | version = "0.8.5" 798 | source = "registry+https://github.com/rust-lang/crates.io-index" 799 | checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" 800 | dependencies = [ 801 | "cfg-if", 802 | "instant", 803 | "libc", 804 | "redox_syscall", 805 | "smallvec", 806 | "winapi", 807 | ] 808 | 809 | [[package]] 810 | name = "percent-encoding" 811 | version = "2.1.0" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 814 | 815 | [[package]] 816 | name = "phf" 817 | version = "0.8.0" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" 820 | dependencies = [ 821 | "phf_macros", 822 | "phf_shared 0.8.0", 823 | "proc-macro-hack", 824 | ] 825 | 826 | [[package]] 827 | name = "phf_codegen" 828 | version = "0.8.0" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" 831 | dependencies = [ 832 | "phf_generator", 833 | "phf_shared 0.8.0", 834 | ] 835 | 836 | [[package]] 837 | name = "phf_generator" 838 | version = "0.8.0" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" 841 | dependencies = [ 842 | "phf_shared 0.8.0", 843 | "rand", 844 | ] 845 | 846 | [[package]] 847 | name = "phf_macros" 848 | version = "0.8.0" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" 851 | dependencies = [ 852 | "phf_generator", 853 | "phf_shared 0.8.0", 854 | "proc-macro-hack", 855 | "proc-macro2", 856 | "quote", 857 | "syn", 858 | ] 859 | 860 | [[package]] 861 | name = "phf_shared" 862 | version = "0.8.0" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" 865 | dependencies = [ 866 | "siphasher", 867 | ] 868 | 869 | [[package]] 870 | name = "phf_shared" 871 | version = "0.10.0" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" 874 | dependencies = [ 875 | "siphasher", 876 | ] 877 | 878 | [[package]] 879 | name = "pin-project-lite" 880 | version = "0.2.6" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" 883 | 884 | [[package]] 885 | name = "pin-utils" 886 | version = "0.1.0" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 889 | 890 | [[package]] 891 | name = "ppv-lite86" 892 | version = "0.2.16" 893 | source = "registry+https://github.com/rust-lang/crates.io-index" 894 | checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" 895 | 896 | [[package]] 897 | name = "precomputed-hash" 898 | version = "0.1.1" 899 | source = "registry+https://github.com/rust-lang/crates.io-index" 900 | checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 901 | 902 | [[package]] 903 | name = "proc-macro-hack" 904 | version = "0.5.19" 905 | source = "registry+https://github.com/rust-lang/crates.io-index" 906 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 907 | 908 | [[package]] 909 | name = "proc-macro-nested" 910 | version = "0.1.7" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" 913 | 914 | [[package]] 915 | name = "proc-macro2" 916 | version = "1.0.56" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" 919 | dependencies = [ 920 | "unicode-ident", 921 | ] 922 | 923 | [[package]] 924 | name = "pulldown-cmark" 925 | version = "0.8.0" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" 928 | dependencies = [ 929 | "bitflags", 930 | "getopts", 931 | "memchr", 932 | "unicase", 933 | ] 934 | 935 | [[package]] 936 | name = "quote" 937 | version = "1.0.9" 938 | source = "registry+https://github.com/rust-lang/crates.io-index" 939 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 940 | dependencies = [ 941 | "proc-macro2", 942 | ] 943 | 944 | [[package]] 945 | name = "rand" 946 | version = "0.7.3" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 949 | dependencies = [ 950 | "getrandom", 951 | "libc", 952 | "rand_chacha", 953 | "rand_core", 954 | "rand_hc", 955 | "rand_pcg", 956 | ] 957 | 958 | [[package]] 959 | name = "rand_chacha" 960 | version = "0.2.2" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 963 | dependencies = [ 964 | "ppv-lite86", 965 | "rand_core", 966 | ] 967 | 968 | [[package]] 969 | name = "rand_core" 970 | version = "0.5.1" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 973 | dependencies = [ 974 | "getrandom", 975 | ] 976 | 977 | [[package]] 978 | name = "rand_hc" 979 | version = "0.2.0" 980 | source = "registry+https://github.com/rust-lang/crates.io-index" 981 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 982 | dependencies = [ 983 | "rand_core", 984 | ] 985 | 986 | [[package]] 987 | name = "rand_pcg" 988 | version = "0.2.1" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" 991 | dependencies = [ 992 | "rand_core", 993 | ] 994 | 995 | [[package]] 996 | name = "redox_syscall" 997 | version = "0.2.10" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 1000 | dependencies = [ 1001 | "bitflags", 1002 | ] 1003 | 1004 | [[package]] 1005 | name = "regex" 1006 | version = "1.5.5" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" 1009 | dependencies = [ 1010 | "aho-corasick", 1011 | "memchr", 1012 | "regex-syntax", 1013 | ] 1014 | 1015 | [[package]] 1016 | name = "regex-syntax" 1017 | version = "0.6.25" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 1020 | 1021 | [[package]] 1022 | name = "reqwest" 1023 | version = "0.11.3" 1024 | source = "registry+https://github.com/rust-lang/crates.io-index" 1025 | checksum = "2296f2fac53979e8ccbc4a1136b25dcefd37be9ed7e4a1f6b05a6029c84ff124" 1026 | dependencies = [ 1027 | "base64", 1028 | "bytes", 1029 | "encoding_rs", 1030 | "futures-core", 1031 | "futures-util", 1032 | "http", 1033 | "http-body", 1034 | "hyper", 1035 | "hyper-rustls", 1036 | "ipnet", 1037 | "js-sys", 1038 | "lazy_static", 1039 | "log", 1040 | "mime", 1041 | "percent-encoding", 1042 | "pin-project-lite", 1043 | "rustls", 1044 | "serde", 1045 | "serde_json", 1046 | "serde_urlencoded", 1047 | "tokio", 1048 | "tokio-rustls", 1049 | "url", 1050 | "wasm-bindgen", 1051 | "wasm-bindgen-futures", 1052 | "web-sys", 1053 | "webpki-roots", 1054 | "winreg", 1055 | ] 1056 | 1057 | [[package]] 1058 | name = "ring" 1059 | version = "0.16.20" 1060 | source = "registry+https://github.com/rust-lang/crates.io-index" 1061 | checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" 1062 | dependencies = [ 1063 | "cc", 1064 | "libc", 1065 | "once_cell", 1066 | "spin", 1067 | "untrusted", 1068 | "web-sys", 1069 | "winapi", 1070 | ] 1071 | 1072 | [[package]] 1073 | name = "rustc-demangle" 1074 | version = "0.1.19" 1075 | source = "registry+https://github.com/rust-lang/crates.io-index" 1076 | checksum = "410f7acf3cb3a44527c5d9546bad4bf4e6c460915d5f9f2fc524498bfe8f70ce" 1077 | 1078 | [[package]] 1079 | name = "rustc_version" 1080 | version = "0.4.0" 1081 | source = "registry+https://github.com/rust-lang/crates.io-index" 1082 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 1083 | dependencies = [ 1084 | "semver", 1085 | ] 1086 | 1087 | [[package]] 1088 | name = "rustls" 1089 | version = "0.19.1" 1090 | source = "registry+https://github.com/rust-lang/crates.io-index" 1091 | checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" 1092 | dependencies = [ 1093 | "base64", 1094 | "log", 1095 | "ring", 1096 | "sct", 1097 | "webpki", 1098 | ] 1099 | 1100 | [[package]] 1101 | name = "ryu" 1102 | version = "1.0.5" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 1105 | 1106 | [[package]] 1107 | name = "scopeguard" 1108 | version = "1.1.0" 1109 | source = "registry+https://github.com/rust-lang/crates.io-index" 1110 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1111 | 1112 | [[package]] 1113 | name = "scraper" 1114 | version = "0.12.0" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "48e02aa790c80c2e494130dec6a522033b6a23603ffc06360e9fe6c611ea2c12" 1117 | dependencies = [ 1118 | "cssparser", 1119 | "ego-tree", 1120 | "getopts", 1121 | "html5ever", 1122 | "matches", 1123 | "selectors", 1124 | "smallvec", 1125 | "tendril", 1126 | ] 1127 | 1128 | [[package]] 1129 | name = "sct" 1130 | version = "0.6.1" 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" 1132 | checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" 1133 | dependencies = [ 1134 | "ring", 1135 | "untrusted", 1136 | ] 1137 | 1138 | [[package]] 1139 | name = "selectors" 1140 | version = "0.22.0" 1141 | source = "registry+https://github.com/rust-lang/crates.io-index" 1142 | checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" 1143 | dependencies = [ 1144 | "bitflags", 1145 | "cssparser", 1146 | "derive_more", 1147 | "fxhash", 1148 | "log", 1149 | "matches", 1150 | "phf", 1151 | "phf_codegen", 1152 | "precomputed-hash", 1153 | "servo_arc", 1154 | "smallvec", 1155 | "thin-slice", 1156 | ] 1157 | 1158 | [[package]] 1159 | name = "semver" 1160 | version = "1.0.6" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "a4a3381e03edd24287172047536f20cabde766e2cd3e65e6b00fb3af51c4f38d" 1163 | 1164 | [[package]] 1165 | name = "serde" 1166 | version = "1.0.125" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" 1169 | dependencies = [ 1170 | "serde_derive", 1171 | ] 1172 | 1173 | [[package]] 1174 | name = "serde_derive" 1175 | version = "1.0.125" 1176 | source = "registry+https://github.com/rust-lang/crates.io-index" 1177 | checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" 1178 | dependencies = [ 1179 | "proc-macro2", 1180 | "quote", 1181 | "syn", 1182 | ] 1183 | 1184 | [[package]] 1185 | name = "serde_json" 1186 | version = "1.0.64" 1187 | source = "registry+https://github.com/rust-lang/crates.io-index" 1188 | checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" 1189 | dependencies = [ 1190 | "itoa", 1191 | "ryu", 1192 | "serde", 1193 | ] 1194 | 1195 | [[package]] 1196 | name = "serde_urlencoded" 1197 | version = "0.7.0" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" 1200 | dependencies = [ 1201 | "form_urlencoded", 1202 | "itoa", 1203 | "ryu", 1204 | "serde", 1205 | ] 1206 | 1207 | [[package]] 1208 | name = "serde_yaml" 1209 | version = "0.8.17" 1210 | source = "registry+https://github.com/rust-lang/crates.io-index" 1211 | checksum = "15654ed4ab61726bf918a39cb8d98a2e2995b002387807fa6ba58fdf7f59bb23" 1212 | dependencies = [ 1213 | "dtoa", 1214 | "linked-hash-map", 1215 | "serde", 1216 | "yaml-rust", 1217 | ] 1218 | 1219 | [[package]] 1220 | name = "servo_arc" 1221 | version = "0.1.1" 1222 | source = "registry+https://github.com/rust-lang/crates.io-index" 1223 | checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" 1224 | dependencies = [ 1225 | "nodrop", 1226 | "stable_deref_trait", 1227 | ] 1228 | 1229 | [[package]] 1230 | name = "siphasher" 1231 | version = "0.3.9" 1232 | source = "registry+https://github.com/rust-lang/crates.io-index" 1233 | checksum = "a86232ab60fa71287d7f2ddae4a7073f6b7aac33631c3015abb556f08c6d0a3e" 1234 | 1235 | [[package]] 1236 | name = "slab" 1237 | version = "0.4.3" 1238 | source = "registry+https://github.com/rust-lang/crates.io-index" 1239 | checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" 1240 | 1241 | [[package]] 1242 | name = "smallvec" 1243 | version = "1.8.0" 1244 | source = "registry+https://github.com/rust-lang/crates.io-index" 1245 | checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" 1246 | 1247 | [[package]] 1248 | name = "socket2" 1249 | version = "0.4.0" 1250 | source = "registry+https://github.com/rust-lang/crates.io-index" 1251 | checksum = "9e3dfc207c526015c632472a77be09cf1b6e46866581aecae5cc38fb4235dea2" 1252 | dependencies = [ 1253 | "libc", 1254 | "winapi", 1255 | ] 1256 | 1257 | [[package]] 1258 | name = "spin" 1259 | version = "0.5.2" 1260 | source = "registry+https://github.com/rust-lang/crates.io-index" 1261 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 1262 | 1263 | [[package]] 1264 | name = "stable_deref_trait" 1265 | version = "1.2.0" 1266 | source = "registry+https://github.com/rust-lang/crates.io-index" 1267 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1268 | 1269 | [[package]] 1270 | name = "string_cache" 1271 | version = "0.8.3" 1272 | source = "registry+https://github.com/rust-lang/crates.io-index" 1273 | checksum = "33994d0838dc2d152d17a62adf608a869b5e846b65b389af7f3dbc1de45c5b26" 1274 | dependencies = [ 1275 | "lazy_static", 1276 | "new_debug_unreachable", 1277 | "parking_lot", 1278 | "phf_shared 0.10.0", 1279 | "precomputed-hash", 1280 | "serde", 1281 | ] 1282 | 1283 | [[package]] 1284 | name = "string_cache_codegen" 1285 | version = "0.5.1" 1286 | source = "registry+https://github.com/rust-lang/crates.io-index" 1287 | checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97" 1288 | dependencies = [ 1289 | "phf_generator", 1290 | "phf_shared 0.8.0", 1291 | "proc-macro2", 1292 | "quote", 1293 | ] 1294 | 1295 | [[package]] 1296 | name = "syn" 1297 | version = "1.0.109" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1300 | dependencies = [ 1301 | "proc-macro2", 1302 | "quote", 1303 | "unicode-ident", 1304 | ] 1305 | 1306 | [[package]] 1307 | name = "synstructure" 1308 | version = "0.12.4" 1309 | source = "registry+https://github.com/rust-lang/crates.io-index" 1310 | checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" 1311 | dependencies = [ 1312 | "proc-macro2", 1313 | "quote", 1314 | "syn", 1315 | "unicode-xid", 1316 | ] 1317 | 1318 | [[package]] 1319 | name = "tendril" 1320 | version = "0.4.2" 1321 | source = "registry+https://github.com/rust-lang/crates.io-index" 1322 | checksum = "a9ef557cb397a4f0a5a3a628f06515f78563f2209e64d47055d9dc6052bf5e33" 1323 | dependencies = [ 1324 | "futf", 1325 | "mac", 1326 | "utf-8", 1327 | ] 1328 | 1329 | [[package]] 1330 | name = "termcolor" 1331 | version = "1.1.2" 1332 | source = "registry+https://github.com/rust-lang/crates.io-index" 1333 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 1334 | dependencies = [ 1335 | "winapi-util", 1336 | ] 1337 | 1338 | [[package]] 1339 | name = "thin-slice" 1340 | version = "0.1.1" 1341 | source = "registry+https://github.com/rust-lang/crates.io-index" 1342 | checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" 1343 | 1344 | [[package]] 1345 | name = "time" 1346 | version = "0.1.44" 1347 | source = "registry+https://github.com/rust-lang/crates.io-index" 1348 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 1349 | dependencies = [ 1350 | "libc", 1351 | "wasi 0.10.0+wasi-snapshot-preview1", 1352 | "winapi", 1353 | ] 1354 | 1355 | [[package]] 1356 | name = "tinyvec" 1357 | version = "1.2.0" 1358 | source = "registry+https://github.com/rust-lang/crates.io-index" 1359 | checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" 1360 | dependencies = [ 1361 | "tinyvec_macros", 1362 | ] 1363 | 1364 | [[package]] 1365 | name = "tinyvec_macros" 1366 | version = "0.1.0" 1367 | source = "registry+https://github.com/rust-lang/crates.io-index" 1368 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1369 | 1370 | [[package]] 1371 | name = "tokio" 1372 | version = "1.8.4" 1373 | source = "registry+https://github.com/rust-lang/crates.io-index" 1374 | checksum = "50dae83881bc9b0403dd5b44ea9deed3e939856cc8722d5be37f0d6e5c6d53dd" 1375 | dependencies = [ 1376 | "autocfg", 1377 | "bytes", 1378 | "libc", 1379 | "memchr", 1380 | "mio", 1381 | "num_cpus", 1382 | "pin-project-lite", 1383 | "tokio-macros", 1384 | "winapi", 1385 | ] 1386 | 1387 | [[package]] 1388 | name = "tokio-macros" 1389 | version = "1.1.0" 1390 | source = "registry+https://github.com/rust-lang/crates.io-index" 1391 | checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57" 1392 | dependencies = [ 1393 | "proc-macro2", 1394 | "quote", 1395 | "syn", 1396 | ] 1397 | 1398 | [[package]] 1399 | name = "tokio-rustls" 1400 | version = "0.22.0" 1401 | source = "registry+https://github.com/rust-lang/crates.io-index" 1402 | checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" 1403 | dependencies = [ 1404 | "rustls", 1405 | "tokio", 1406 | "webpki", 1407 | ] 1408 | 1409 | [[package]] 1410 | name = "tokio-util" 1411 | version = "0.7.2" 1412 | source = "registry+https://github.com/rust-lang/crates.io-index" 1413 | checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c" 1414 | dependencies = [ 1415 | "bytes", 1416 | "futures-core", 1417 | "futures-sink", 1418 | "pin-project-lite", 1419 | "tokio", 1420 | "tracing", 1421 | ] 1422 | 1423 | [[package]] 1424 | name = "tower-service" 1425 | version = "0.3.1" 1426 | source = "registry+https://github.com/rust-lang/crates.io-index" 1427 | checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" 1428 | 1429 | [[package]] 1430 | name = "tracing" 1431 | version = "0.1.26" 1432 | source = "registry+https://github.com/rust-lang/crates.io-index" 1433 | checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d" 1434 | dependencies = [ 1435 | "cfg-if", 1436 | "pin-project-lite", 1437 | "tracing-attributes", 1438 | "tracing-core", 1439 | ] 1440 | 1441 | [[package]] 1442 | name = "tracing-attributes" 1443 | version = "0.1.23" 1444 | source = "registry+https://github.com/rust-lang/crates.io-index" 1445 | checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" 1446 | dependencies = [ 1447 | "proc-macro2", 1448 | "quote", 1449 | "syn", 1450 | ] 1451 | 1452 | [[package]] 1453 | name = "tracing-core" 1454 | version = "0.1.18" 1455 | source = "registry+https://github.com/rust-lang/crates.io-index" 1456 | checksum = "a9ff14f98b1a4b289c6248a023c1c2fa1491062964e9fed67ab29c4e4da4a052" 1457 | dependencies = [ 1458 | "lazy_static", 1459 | ] 1460 | 1461 | [[package]] 1462 | name = "try-lock" 1463 | version = "0.2.3" 1464 | source = "registry+https://github.com/rust-lang/crates.io-index" 1465 | checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" 1466 | 1467 | [[package]] 1468 | name = "unicase" 1469 | version = "2.6.0" 1470 | source = "registry+https://github.com/rust-lang/crates.io-index" 1471 | checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" 1472 | dependencies = [ 1473 | "version_check", 1474 | ] 1475 | 1476 | [[package]] 1477 | name = "unicode-bidi" 1478 | version = "0.3.5" 1479 | source = "registry+https://github.com/rust-lang/crates.io-index" 1480 | checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" 1481 | dependencies = [ 1482 | "matches", 1483 | ] 1484 | 1485 | [[package]] 1486 | name = "unicode-ident" 1487 | version = "1.0.8" 1488 | source = "registry+https://github.com/rust-lang/crates.io-index" 1489 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 1490 | 1491 | [[package]] 1492 | name = "unicode-normalization" 1493 | version = "0.1.17" 1494 | source = "registry+https://github.com/rust-lang/crates.io-index" 1495 | checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" 1496 | dependencies = [ 1497 | "tinyvec", 1498 | ] 1499 | 1500 | [[package]] 1501 | name = "unicode-width" 1502 | version = "0.1.8" 1503 | source = "registry+https://github.com/rust-lang/crates.io-index" 1504 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 1505 | 1506 | [[package]] 1507 | name = "unicode-xid" 1508 | version = "0.2.2" 1509 | source = "registry+https://github.com/rust-lang/crates.io-index" 1510 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 1511 | 1512 | [[package]] 1513 | name = "untrusted" 1514 | version = "0.7.1" 1515 | source = "registry+https://github.com/rust-lang/crates.io-index" 1516 | checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 1517 | 1518 | [[package]] 1519 | name = "url" 1520 | version = "2.2.1" 1521 | source = "registry+https://github.com/rust-lang/crates.io-index" 1522 | checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" 1523 | dependencies = [ 1524 | "form_urlencoded", 1525 | "idna", 1526 | "matches", 1527 | "percent-encoding", 1528 | ] 1529 | 1530 | [[package]] 1531 | name = "utf-8" 1532 | version = "0.7.6" 1533 | source = "registry+https://github.com/rust-lang/crates.io-index" 1534 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 1535 | 1536 | [[package]] 1537 | name = "version_check" 1538 | version = "0.9.3" 1539 | source = "registry+https://github.com/rust-lang/crates.io-index" 1540 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 1541 | 1542 | [[package]] 1543 | name = "want" 1544 | version = "0.3.0" 1545 | source = "registry+https://github.com/rust-lang/crates.io-index" 1546 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 1547 | dependencies = [ 1548 | "log", 1549 | "try-lock", 1550 | ] 1551 | 1552 | [[package]] 1553 | name = "wasi" 1554 | version = "0.9.0+wasi-snapshot-preview1" 1555 | source = "registry+https://github.com/rust-lang/crates.io-index" 1556 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 1557 | 1558 | [[package]] 1559 | name = "wasi" 1560 | version = "0.10.0+wasi-snapshot-preview1" 1561 | source = "registry+https://github.com/rust-lang/crates.io-index" 1562 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 1563 | 1564 | [[package]] 1565 | name = "wasm-bindgen" 1566 | version = "0.2.73" 1567 | source = "registry+https://github.com/rust-lang/crates.io-index" 1568 | checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9" 1569 | dependencies = [ 1570 | "cfg-if", 1571 | "serde", 1572 | "serde_json", 1573 | "wasm-bindgen-macro", 1574 | ] 1575 | 1576 | [[package]] 1577 | name = "wasm-bindgen-backend" 1578 | version = "0.2.73" 1579 | source = "registry+https://github.com/rust-lang/crates.io-index" 1580 | checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae" 1581 | dependencies = [ 1582 | "bumpalo", 1583 | "lazy_static", 1584 | "log", 1585 | "proc-macro2", 1586 | "quote", 1587 | "syn", 1588 | "wasm-bindgen-shared", 1589 | ] 1590 | 1591 | [[package]] 1592 | name = "wasm-bindgen-futures" 1593 | version = "0.4.23" 1594 | source = "registry+https://github.com/rust-lang/crates.io-index" 1595 | checksum = "81b8b767af23de6ac18bf2168b690bed2902743ddf0fb39252e36f9e2bfc63ea" 1596 | dependencies = [ 1597 | "cfg-if", 1598 | "js-sys", 1599 | "wasm-bindgen", 1600 | "web-sys", 1601 | ] 1602 | 1603 | [[package]] 1604 | name = "wasm-bindgen-macro" 1605 | version = "0.2.73" 1606 | source = "registry+https://github.com/rust-lang/crates.io-index" 1607 | checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f" 1608 | dependencies = [ 1609 | "quote", 1610 | "wasm-bindgen-macro-support", 1611 | ] 1612 | 1613 | [[package]] 1614 | name = "wasm-bindgen-macro-support" 1615 | version = "0.2.73" 1616 | source = "registry+https://github.com/rust-lang/crates.io-index" 1617 | checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c" 1618 | dependencies = [ 1619 | "proc-macro2", 1620 | "quote", 1621 | "syn", 1622 | "wasm-bindgen-backend", 1623 | "wasm-bindgen-shared", 1624 | ] 1625 | 1626 | [[package]] 1627 | name = "wasm-bindgen-shared" 1628 | version = "0.2.73" 1629 | source = "registry+https://github.com/rust-lang/crates.io-index" 1630 | checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489" 1631 | 1632 | [[package]] 1633 | name = "web-sys" 1634 | version = "0.3.50" 1635 | source = "registry+https://github.com/rust-lang/crates.io-index" 1636 | checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be" 1637 | dependencies = [ 1638 | "js-sys", 1639 | "wasm-bindgen", 1640 | ] 1641 | 1642 | [[package]] 1643 | name = "webpki" 1644 | version = "0.21.4" 1645 | source = "registry+https://github.com/rust-lang/crates.io-index" 1646 | checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" 1647 | dependencies = [ 1648 | "ring", 1649 | "untrusted", 1650 | ] 1651 | 1652 | [[package]] 1653 | name = "webpki-roots" 1654 | version = "0.21.1" 1655 | source = "registry+https://github.com/rust-lang/crates.io-index" 1656 | checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" 1657 | dependencies = [ 1658 | "webpki", 1659 | ] 1660 | 1661 | [[package]] 1662 | name = "winapi" 1663 | version = "0.3.9" 1664 | source = "registry+https://github.com/rust-lang/crates.io-index" 1665 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1666 | dependencies = [ 1667 | "winapi-i686-pc-windows-gnu", 1668 | "winapi-x86_64-pc-windows-gnu", 1669 | ] 1670 | 1671 | [[package]] 1672 | name = "winapi-i686-pc-windows-gnu" 1673 | version = "0.4.0" 1674 | source = "registry+https://github.com/rust-lang/crates.io-index" 1675 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1676 | 1677 | [[package]] 1678 | name = "winapi-util" 1679 | version = "0.1.5" 1680 | source = "registry+https://github.com/rust-lang/crates.io-index" 1681 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1682 | dependencies = [ 1683 | "winapi", 1684 | ] 1685 | 1686 | [[package]] 1687 | name = "winapi-x86_64-pc-windows-gnu" 1688 | version = "0.4.0" 1689 | source = "registry+https://github.com/rust-lang/crates.io-index" 1690 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1691 | 1692 | [[package]] 1693 | name = "winreg" 1694 | version = "0.7.0" 1695 | source = "registry+https://github.com/rust-lang/crates.io-index" 1696 | checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" 1697 | dependencies = [ 1698 | "winapi", 1699 | ] 1700 | 1701 | [[package]] 1702 | name = "yaml-rust" 1703 | version = "0.4.5" 1704 | source = "registry+https://github.com/rust-lang/crates.io-index" 1705 | checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" 1706 | dependencies = [ 1707 | "linked-hash-map", 1708 | ] 1709 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "awesome-rust" 3 | version = "0.1.0" 4 | authors = [] 5 | homepage = "https://github.com/rust-unofficial/awesome-rust" 6 | repository = "https://github.com/rust-unofficial/awesome-rust" 7 | edition = "2018" 8 | default-run = "awesome-rust" 9 | 10 | [dependencies] 11 | pulldown-cmark = "0.8" 12 | futures = "0.3" 13 | reqwest = { version="0.11", default_features=false, features=["rustls-tls", "json"] } 14 | tokio = {version = "1", features = ["macros", "rt", "rt-multi-thread", "time"] } 15 | serde = { version = "1.0", features = ["derive"] } 16 | serde_yaml = "0.8" 17 | hyper = "0.14" 18 | failure = "0.1" 19 | lazy_static = "1" 20 | env_logger = "0.8" 21 | log = "0.4" 22 | regex = "1" 23 | scraper = "0.12" 24 | chrono = { version = "0.4", features = ["serde"] } 25 | chrono-humanize = "0.2" 26 | diffy = "0.3" 27 | serde_json = "*" 28 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://raw.githubusercontent.com/shuttle-hq/shuttle/master/assets/logo-rectangle-transparent.png) 2 | # Awesome Shuttle 3 | 4 | An awesome list of Shuttle-hosted projects and resources that users can add to. 5 | 6 | If you want to contribute, please read [this](CONTRIBUTING.md). 7 | 8 | ## Table of contents 9 | 10 | 11 | 12 | - [Official Pages](#official-pages) 13 | - [Official Tutorials](#official-tutorials) 14 | - [Official Examples and Community Examples](#official-examples-and-community-examples) 15 | - [Workshops](#workshops) 16 | - [Built on Shuttle](#built-on-shuttle) 17 | - [Libraries](#libraries) 18 | - [Resources](#resources) 19 | - [License](#license) 20 | 21 | 22 | 23 | ## Official Pages 24 | 25 | - [Status Page](status.shuttle.rs) - Status page for Shuttle services. If anything goes down, you can check here. 26 | - [Docs](docs.shuttle.rs): The Shuttle docs. 27 | 28 | ## Official Tutorials 29 | 30 | Official Shuttle tutorials to help you write more competent services. 31 | 32 | - [Working with Databases in Rust](https://docs.shuttle.rs/tutorials/databases-with-rust) - A guide on how to work with databases in Rust. 33 | - [Writing a REST HTTP service with Axum](https://docs.shuttle.rs/tutorials/rest-http-service-with-axum) - A guide on how to write a competent HTTP service in Axum that covers static files, cookies, middleware and more. 34 | - [Writing a Custom Service](https://docs.shuttle.rs/tutorials/custom-service) - A guide on how to write a web service that implements multiple services (so for example, a Discord bot and router that you can potentially extend and add some background tasks to) 35 | 36 | ## Official Examples and Community Examples 37 | 38 | Find full Shuttle app examples in the [shuttle-examples](https://github.com/shuttle-hq/shuttle-examples) repository and use them as templates for your next project. 39 | Also check out the [Community Examples](https://github.com/shuttle-hq/shuttle-examples#community-examples) for even more examples. 40 | 41 | ## Workshops 42 | 43 | Shuttle workshops that have been held in the past. 44 | 45 | - [Re-writing an Express.js Chat App in Rust](https://www.youtube.com/watch?v=-N8AKKCE9L8&t=708s) - A workshop that details writing a web-socket based real time chat app in Rust with Axum. 46 | - [Building Semantic Search in Rust with Qdrant & Shuttle](https://www.youtube.com/watch?v=YLWSeiDh2o0) - A workshop that details leveraging the power of LLMs to write a Semantic Search AI in Rust, using Qdrant (an open-source vector search database) and OpenAI. 47 | 48 | ## Built on Shuttle 49 | 50 | A list of cool web-based applications that have been built on Shuttle. 51 | 52 | - [Kitsune](https://github.com/aumetra/kitsune/tree/aumetra/shuttle) - A self-hostable ActivityPub-based social media server, like Mastodon. Uses Vue as a frontend. 53 | - [Rustypaste](https://github.com/orhun/rustypaste) - A simple web service that lets you host and share long plain text. 54 | - [Github API Dashboard](https://github.com/marc2332/ghboard) - A dashboard that tracks your Github contributions. Uses Dioxus as a frontend, powered by the Github GraphQL API. [(Demo)](https://ghboard.shuttleapp.rs/user/demonthos) 55 | - [no-more-json](https://github.com/beyarkay/no-more-json) - Fed up with massive JSON objects? Just give `no-more-json` an API endpoint and a [`jq`](https://jqlang.github.io/jq/) query. `no-more-json` will apply that query to the JSON returned by the endpoint, and give you the (much smaller) result. 56 | - [Licensebat](https://github.com/licensebat/licensebat) - A tool to help you verify that your dependencies comply with your license policies 57 | - [plabayo.tech](https://plabayo.tech/): [open source](https://github.com/plabayo/website) official company website of Plabayo, a FOSS dev and consultancy studio 58 | - [bckt.xyz](https://bckt.xyz): [open source](https://github.com/plabayo/bucket) link shortener and secret sharing service (for friends of Plabayo) 59 | 60 | ## Libraries 61 | 62 | Plugins for Shuttle that users have made. 63 | 64 | - [shuttle-diesel-async](https://github.com/aumetra/shuttle-diesel-async) - A plugin for Shuttle that lets you use Diesel as the provided connection instead of SQLx. 65 | - [shuttle-seaorm](https://github.com/joshua-mo-143/shuttle-seaorm) - A plugin for Shuttle that lets you use SeaORM as the provided connection instead of SQLx. 66 | 67 | ## Resources 68 | 69 | Unofficial Shuttle tutorials. 70 | 71 | - [Building a SaaS with Rust](https://joshmo.bearblog.dev/lets-build-a-saas-with-rust/) - A guide for writing a SaaS backend in Rust. 72 | - [Building a Movie Collection Manager](https://bcnrust.github.io/devbcn-workshop/) - Full Stack Workshop with Rust, Actix, SQLx, Dioxus, and Shuttle. 73 | 74 | ## License 75 | 76 | [![CC0](https://licensebuttons.net/p/zero/1.0/88x31.png)](https://creativecommons.org/publicdomain/zero/1.0/) 77 | -------------------------------------------------------------------------------- /src/bin/cleanup.rs: -------------------------------------------------------------------------------- 1 | // Cleans up `README.md` 2 | // Usage: cargo run --bin cleanup 3 | 4 | use std::fs; 5 | use std::fs::File; 6 | use std::io::Read; 7 | 8 | fn fix_dashes(lines: Vec) -> Vec { 9 | let mut fixed_lines: Vec = Vec::with_capacity(lines.len()); 10 | 11 | let mut within_content = false; 12 | 13 | for line in lines { 14 | if within_content { 15 | fixed_lines.push(line.replace(" - ", " — ")); 16 | } else { 17 | if line.starts_with("## Applications") { 18 | within_content = true; 19 | } 20 | 21 | fixed_lines.push(line.to_string()); 22 | } 23 | } 24 | 25 | return fixed_lines; 26 | } 27 | 28 | fn main() { 29 | // Read the awesome file. 30 | let mut file = File::open("README.md").expect("Failed to read the file"); 31 | 32 | let mut contents = String::new(); 33 | file.read_to_string(&mut contents) 34 | .expect("Failed to read file contents"); 35 | 36 | // Split contents into lines. 37 | let lines: Vec = contents.lines().map(|l| l.to_string()).collect(); 38 | 39 | // Fix the dashes. 40 | let fixed_contents = fix_dashes(lines); 41 | 42 | // Write the awesome file. 43 | fs::write("README.md", fixed_contents.join("\n").as_bytes()) 44 | .expect("Failed to write to the file"); 45 | } 46 | -------------------------------------------------------------------------------- /src/bin/hacktoberfest.rs: -------------------------------------------------------------------------------- 1 | // Helper tool to dump all repos in awesome-rust that are tagged with "hacktoberfest" 2 | 3 | use chrono::{DateTime, Duration, Local}; 4 | use failure::{format_err, Error, Fail}; 5 | use futures::future::{select_all, BoxFuture, FutureExt}; 6 | use lazy_static::lazy_static; 7 | use log::{debug, warn}; 8 | use pulldown_cmark::{Event, Parser, Tag}; 9 | use regex::Regex; 10 | use reqwest::redirect::Policy; 11 | use reqwest::Client; 12 | use serde::{Deserialize, Serialize}; 13 | use std::collections::{BTreeMap, BTreeSet}; 14 | use std::env; 15 | use std::fs; 16 | use std::io::Write; 17 | use std::time; 18 | use std::u8; 19 | use tokio::sync::Semaphore; 20 | use tokio::sync::SemaphorePermit; 21 | 22 | #[derive(Debug, Fail, Serialize, Deserialize)] 23 | enum CheckerError { 24 | #[fail(display = "http error: {}", status)] 25 | HttpError { 26 | status: u16, 27 | location: Option, 28 | }, 29 | } 30 | 31 | struct MaxHandles { 32 | remaining: Semaphore, 33 | } 34 | 35 | struct Handle<'a> { 36 | _permit: SemaphorePermit<'a>, 37 | } 38 | 39 | impl MaxHandles { 40 | fn new(max: usize) -> MaxHandles { 41 | MaxHandles { 42 | remaining: Semaphore::new(max), 43 | } 44 | } 45 | 46 | async fn get<'a>(&'a self) -> Handle<'a> { 47 | let permit = self.remaining.acquire().await.unwrap(); 48 | return Handle { _permit: permit }; 49 | } 50 | } 51 | 52 | impl<'a> Drop for Handle<'a> { 53 | fn drop(&mut self) { 54 | debug!("Dropping"); 55 | } 56 | } 57 | 58 | lazy_static! { 59 | static ref CLIENT: Client = Client::builder() 60 | .danger_accept_invalid_certs(true) // because some certs are out of date 61 | .user_agent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:68.0) Gecko/20100101 Firefox/68.0") // so some sites (e.g. sciter.com) don't reject us 62 | .redirect(Policy::none()) 63 | .pool_max_idle_per_host(0) 64 | .timeout(time::Duration::from_secs(20)) 65 | .build().unwrap(); 66 | 67 | // This is to avoid errors with running out of file handles, so we only do 20 requests at a time 68 | static ref HANDLES: MaxHandles = MaxHandles::new(20); 69 | } 70 | 71 | lazy_static! { 72 | static ref GITHUB_REPO_REGEX: Regex = 73 | Regex::new(r"^https://github.com/(?P[^/]+)/(?P[^/]+)/?$").unwrap(); 74 | static ref GITHUB_API_REGEX: Regex = Regex::new(r"https://api.github.com/").unwrap(); 75 | } 76 | 77 | #[derive(Deserialize, Debug)] 78 | struct RepoInfo { 79 | full_name: String, 80 | description: Option, 81 | topics: Vec, 82 | } 83 | 84 | async fn get_hacktoberfest_core(github_url: String) -> Result { 85 | warn!("Downloading Hacktoberfest label for {}", github_url); 86 | let rewritten = GITHUB_REPO_REGEX 87 | .replace_all(&github_url, "https://api.github.com/repos/$org/$repo") 88 | .to_string(); 89 | let mut req = CLIENT.get(&rewritten); 90 | if let Ok(username) = env::var("USERNAME_FOR_GITHUB") { 91 | if let Ok(password) = env::var("TOKEN_FOR_GITHUB") { 92 | // needs a token with at least public_repo scope 93 | req = req.basic_auth(username, Some(password)); 94 | } 95 | } 96 | 97 | let resp = req.send().await; 98 | match resp { 99 | Err(err) => { 100 | warn!("Error while getting {}: {}", github_url, err); 101 | return Err(CheckerError::HttpError { 102 | status: err.status().unwrap().as_u16(), 103 | location: Some(github_url.to_string()), 104 | }); 105 | } 106 | Ok(ok) => { 107 | if !ok.status().is_success() { 108 | return Err(CheckerError::HttpError { 109 | status: ok.status().as_u16(), 110 | location: None, 111 | }); 112 | } 113 | let raw = ok.text().await.unwrap(); 114 | match serde_json::from_str::(&raw) { 115 | Ok(val) => Ok(Info { 116 | name: val.full_name, 117 | description: val.description.unwrap_or_default(), 118 | hacktoberfest: val.topics.iter().find(|t| *t == "hacktoberfest").is_some(), 119 | }), 120 | Err(_) => { 121 | panic!("{}", raw); 122 | } 123 | } 124 | } 125 | } 126 | } 127 | 128 | fn get_hacktoberfest(url: String) -> BoxFuture<'static, (String, Result)> { 129 | debug!("Need handle for {}", url); 130 | async move { 131 | let _handle = HANDLES.get().await; 132 | return (url.clone(), get_hacktoberfest_core(url).await); 133 | } 134 | .boxed() 135 | } 136 | 137 | #[derive(Debug, Serialize, Deserialize)] 138 | struct Info { 139 | hacktoberfest: bool, 140 | name: String, 141 | description: String, 142 | } 143 | 144 | #[derive(Debug, Serialize, Deserialize)] 145 | struct Link { 146 | updated_at: DateTime, 147 | info: Info, 148 | } 149 | 150 | type Results = BTreeMap; 151 | 152 | #[tokio::main] 153 | async fn main() -> Result<(), Error> { 154 | env_logger::init(); 155 | let markdown_input = fs::read_to_string("README.md").expect("Can't read README.md"); 156 | let parser = Parser::new(&markdown_input); 157 | 158 | let mut used: BTreeSet = BTreeSet::new(); 159 | let mut results: Results = fs::read_to_string("results/hacktoberfest.yaml") 160 | .map_err(|e| format_err!("{}", e)) 161 | .and_then(|x| serde_yaml::from_str(&x).map_err(|e| format_err!("{}", e))) 162 | .unwrap_or(Results::new()); 163 | 164 | let mut url_checks = vec![]; 165 | 166 | let mut do_check = |url: String| { 167 | if !url.starts_with("http") { 168 | return; 169 | } 170 | if used.contains(&url) { 171 | return; 172 | } 173 | used.insert(url.clone()); 174 | if let Some(_) = results.get(&url) { 175 | return; 176 | } 177 | let check = get_hacktoberfest(url).boxed(); 178 | url_checks.push(check); 179 | }; 180 | 181 | let mut to_check: Vec = vec![]; 182 | 183 | for (event, _) in parser.into_offset_iter() { 184 | match event { 185 | Event::Start(tag) => match tag { 186 | Tag::Link(_link_type, url, _title) | Tag::Image(_link_type, url, _title) => { 187 | if GITHUB_REPO_REGEX.is_match(&url) { 188 | to_check.push(url.to_string()); 189 | } 190 | } 191 | _ => {} 192 | }, 193 | _ => {} 194 | } 195 | } 196 | 197 | for url in to_check { 198 | do_check(url) 199 | } 200 | 201 | let results_keys = results.keys().cloned().collect::>(); 202 | let old_links = results_keys.difference(&used); 203 | for link in old_links { 204 | results.remove(link).unwrap(); 205 | } 206 | fs::write("results/results.yaml", serde_yaml::to_string(&results)?)?; 207 | 208 | let mut not_written = 0; 209 | let mut last_written = Local::now(); 210 | 211 | let mut failed: u32 = 0; 212 | while url_checks.len() > 0 { 213 | debug!("Waiting for {}", url_checks.len()); 214 | let ((url, res), _index, remaining) = select_all(url_checks).await; 215 | url_checks = remaining; 216 | match res { 217 | Ok(info) => { 218 | print!("\u{2714} "); 219 | if let Some(link) = results.get_mut(&url) { 220 | link.updated_at = Local::now(); 221 | link.info = info 222 | } else { 223 | results.insert( 224 | url.clone(), 225 | Link { 226 | updated_at: Local::now(), 227 | info: info, 228 | }, 229 | ); 230 | } 231 | } 232 | Err(_) => { 233 | print!("\u{2718} "); 234 | println!("{}", url); 235 | failed += 1; 236 | } 237 | } 238 | std::io::stdout().flush().unwrap(); 239 | 240 | not_written += 1; 241 | let duration = Local::now() - last_written; 242 | if duration > Duration::seconds(5) || not_written > 20 { 243 | fs::write( 244 | "results/hacktoberfest.yaml", 245 | serde_yaml::to_string(&results)?, 246 | )?; 247 | not_written = 0; 248 | last_written = Local::now(); 249 | } 250 | } 251 | fs::write( 252 | "results/hacktoberfest.yaml", 253 | serde_yaml::to_string(&results)?, 254 | )?; 255 | println!(""); 256 | 257 | if failed == 0 { 258 | println!("All awesome-rust repos tagged with 'hacktoberfest'"); 259 | let mut sorted_repos = results 260 | .keys() 261 | .map(|s| s.to_string()) 262 | .collect::>(); 263 | sorted_repos.sort_by_key(|a| a.to_lowercase()); 264 | for name in sorted_repos { 265 | let link = results.get(&name).unwrap(); 266 | if link.info.hacktoberfest { 267 | println!( 268 | "* [{}]({}) - {}", 269 | link.info.name, name, link.info.description 270 | ) 271 | } 272 | } 273 | Ok(()) 274 | } else { 275 | Err(format_err!("{} urls with errors", failed)) 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Duration, Local}; 2 | use diffy::create_patch; 3 | use failure::{format_err, Error, Fail}; 4 | use futures::future::{select_all, BoxFuture, FutureExt}; 5 | use lazy_static::lazy_static; 6 | use log::{debug, info, warn}; 7 | use pulldown_cmark::{Event, Parser, Tag}; 8 | use regex::Regex; 9 | use reqwest::{header, redirect::Policy, Client, StatusCode, Url}; 10 | use serde::{Deserialize, Serialize}; 11 | use std::collections::{BTreeMap, BTreeSet}; 12 | use std::env; 13 | use std::io::Write; 14 | use std::time; 15 | use std::u8; 16 | use std::{cmp::Ordering, fs}; 17 | use tokio::sync::Semaphore; 18 | use tokio::sync::SemaphorePermit; 19 | 20 | const MINIMUM_GITHUB_STARS: u32 = 50; 21 | const MINIMUM_CARGO_DOWNLOADS: u32 = 2000; 22 | 23 | // Allow overriding the needed stars for a section. "level" is the header level in the markdown, default is MINIMUM_GITHUB_STARS 24 | // In general, we should just use the defaults. However, for some areas where there's not a lot of well-starred projects, but a 25 | // a few that are say just below the thresholds, then it's worth reducing the thresholds so we can get a few more projects. 26 | fn override_stars(level: u32, text: &str) -> Option { 27 | if level == 2 && text.contains("Resources") { 28 | // This is zero because a lot of the resources are non-github/non-cargo links and overriding for all would be annoying 29 | // These should be evaluated with more primitive means 30 | Some(0) 31 | } else if level == 3 && text.contains("Games") { 32 | Some(40) 33 | } else if level == 3 && text.contains("Emulators") { 34 | Some(40) 35 | } else { 36 | None // i.e. use defaults 37 | } 38 | } 39 | 40 | lazy_static! { 41 | // Overrides for popularity count, each needs a good reason (i.e. downloads/stars we don't support automatic counting of) 42 | // Each is a URL that's "enough" for an item to pass the popularity checks 43 | static ref POPULARITY_OVERRIDES: Vec = vec![ 44 | "https://github.com/maidsafe".to_string(), // Many repos of Rust code, collectively > 50 stars 45 | "https://pijul.org".to_string(), // Uses it's own VCS at https://nest.pijul.com/pijul/pijul with 190 stars at last check 46 | "https://gitlab.com/veloren/veloren".to_string(), // No direct gitlab support, but >1000 stars there 47 | "https://gitlab.redox-os.org/redox-os/redox".to_string(), // 394 stars 48 | "https://amp.rs".to_string(), // https://github.com/jmacdonald/amp has 2.9k stars 49 | "https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb".to_string(), // > 350k downloads 50 | "https://gitpod.io".to_string(), // https://github.com/gitpod-io/gitpod has 4.7k stars 51 | "https://wiki.gnome.org/Apps/Builder".to_string(), // https://gitlab.gnome.org/GNOME/gnome-builder has 133 stars 52 | "https://marketplace.visualstudio.com/items?itemName=bungcip.better-toml".to_string(), // > 860k downloads 53 | "https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer".to_string(), // > 260k downloads 54 | "https://marketplace.visualstudio.com/items?itemName=rust-lang.rust".to_string(), // > 1M downloads 55 | "https://docs.rs".to_string(), // https://github.com/rust-lang/docs.rs has >600 stars 56 | "https://github.com/rust-bio".to_string(), // https://github.com/rust-bio/rust-bio on it's own has >900 stars 57 | "https://github.com/contain-rs".to_string(), // Lots of repos with good star counts 58 | "https://github.com/georust".to_string(), // Lots of repos with good star counts 59 | "http://kiss3d.org".to_string(), // https://github.com/sebcrozet/kiss3d has >900 stars 60 | "https://github.com/rust-qt".to_string(), // Various high-stars repositories 61 | "https://chromium.googlesource.com/chromiumos/platform/crosvm/".to_string(), // Can't tell count directly, but various mirrors of it (e.g. https://github.com/dgreid/crosvm) have enough stars that it's got enough interest 62 | "https://seed-rs.org/".to_string(), // https://github.com/seed-rs/seed has 2.1k stars 63 | "https://crates.io".to_string(), // This one gets a free pass :) 64 | "https://cloudsmith.com/product/formats/cargo-registry".to_string(), // First private cargo registry (https://cloudsmith.com/blog/worlds-first-private-cargo-registry-w-cloudsmith-rust/) and not much in the way of other options yet. See also https://github.com/rust-unofficial/awesome-rust/pull/1141#discussion_r688711555 65 | "https://gitlab.com/ttyperacer/terminal-typeracer".to_string(), // GitLab repo with >40 stars. 66 | "https://github.com/esp-rs".to_string(), // Espressif Rust Organization (Organizations have no stars). 67 | "https://github.com/arkworks-rs".to_string(), // Rust ecosystem for zkSNARK programming (Organizations have no stars) 68 | "https://marketplace.visualstudio.com/items?itemName=jinxdash.prettier-rust".to_string(), // https://github.com/jinxdash/prettier-plugin-rust has >50 stars 69 | ]; 70 | } 71 | 72 | #[derive(Debug, Fail, Serialize, Deserialize)] 73 | enum CheckerError { 74 | #[fail(display = "failed to try url")] 75 | NotTried, // Generally shouldn't happen, but useful to have 76 | 77 | #[fail(display = "http error: {}", status)] 78 | HttpError { 79 | status: u16, 80 | location: Option, 81 | }, 82 | 83 | #[fail(display = "too many requests")] 84 | TooManyRequests, 85 | 86 | #[fail(display = "reqwest error: {}", error)] 87 | ReqwestError { error: String }, 88 | 89 | #[fail(display = "travis build is unknown")] 90 | TravisBuildUnknown, 91 | 92 | #[fail(display = "travis build image with no branch")] 93 | TravisBuildNoBranch, 94 | } 95 | 96 | fn formatter(err: &CheckerError, url: &String) -> String { 97 | match err { 98 | CheckerError::HttpError { status, location } => match location { 99 | Some(loc) => { 100 | format!("[{}] {} -> {}", status, url, loc) 101 | } 102 | None => { 103 | format!("[{}] {}", status, url) 104 | } 105 | }, 106 | CheckerError::TravisBuildUnknown => { 107 | format!("[Unknown travis build] {}", url) 108 | } 109 | CheckerError::TravisBuildNoBranch => { 110 | format!("[Travis build image with no branch specified] {}", url) 111 | } 112 | _ => { 113 | format!("{:?}", err) 114 | } 115 | } 116 | } 117 | 118 | struct MaxHandles { 119 | remaining: Semaphore, 120 | } 121 | 122 | struct Handle<'a> { 123 | _permit: SemaphorePermit<'a>, 124 | } 125 | 126 | impl MaxHandles { 127 | fn new(max: usize) -> MaxHandles { 128 | MaxHandles { 129 | remaining: Semaphore::new(max), 130 | } 131 | } 132 | 133 | async fn get<'a>(&'a self) -> Handle<'a> { 134 | let permit = self.remaining.acquire().await.unwrap(); 135 | return Handle { _permit: permit }; 136 | } 137 | } 138 | 139 | impl<'a> Drop for Handle<'a> { 140 | fn drop(&mut self) { 141 | debug!("Dropping"); 142 | } 143 | } 144 | 145 | lazy_static! { 146 | static ref CLIENT: Client = Client::builder() 147 | .danger_accept_invalid_certs(true) // because some certs are out of date 148 | .user_agent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:68.0) Gecko/20100101 Firefox/68.0") // so some sites (e.g. sciter.com) don't reject us 149 | .redirect(Policy::none()) 150 | .pool_max_idle_per_host(0) 151 | .timeout(time::Duration::from_secs(20)) 152 | .build().unwrap(); 153 | 154 | // This is to avoid errors with running out of file handles, so we only do 20 requests at a time 155 | static ref HANDLES: MaxHandles = MaxHandles::new(20); 156 | } 157 | 158 | fn get_url(url: String) -> BoxFuture<'static, (String, Result<(), CheckerError>)> { 159 | debug!("Need handle for {}", url); 160 | async move { 161 | let _handle = HANDLES.get().await; 162 | return get_url_core(url).await; 163 | } 164 | .boxed() 165 | } 166 | 167 | lazy_static! { 168 | static ref GITHUB_REPO_REGEX: Regex = 169 | Regex::new(r"^https://github.com/(?P[^/]+)/(?P[^/]+)(.*)").unwrap(); 170 | static ref GITHUB_API_REGEX: Regex = Regex::new(r"https://api.github.com/").unwrap(); 171 | static ref CRATE_REGEX: Regex = 172 | Regex::new(r"https://crates.io/crates/(?P[^/]+)/?$").unwrap(); 173 | } 174 | 175 | #[derive(Deserialize, Debug)] 176 | struct GithubStars { 177 | stargazers_count: u32, 178 | archived: bool, 179 | } 180 | 181 | async fn get_stars(github_url: &str) -> Option { 182 | warn!("Downloading Github stars for {}", github_url); 183 | let rewritten = GITHUB_REPO_REGEX 184 | .replace_all(&github_url, "https://api.github.com/repos/$org/$repo") 185 | .to_string(); 186 | let mut req = CLIENT.get(&rewritten); 187 | if let Ok(username) = env::var("USERNAME_FOR_GITHUB") { 188 | if let Ok(password) = env::var("TOKEN_FOR_GITHUB") { 189 | // needs a token with at least public_repo scope 190 | req = req.basic_auth(username, Some(password)); 191 | } 192 | } 193 | 194 | let resp = req.send().await; 195 | match resp { 196 | Err(err) => { 197 | warn!("Error while getting {}: {}", github_url, err); 198 | return None; 199 | } 200 | Ok(ok) => { 201 | let raw = ok.text().await.unwrap(); 202 | let data = match serde_json::from_str::(&raw) { 203 | Ok(val) => val, 204 | Err(_) => { 205 | panic!("{:?}", raw); 206 | } 207 | }; 208 | if data.archived { 209 | warn!("{} is archived, so ignoring stars", github_url); 210 | return Some(0); 211 | } 212 | return Some(data.stargazers_count); 213 | } 214 | } 215 | } 216 | 217 | #[derive(Deserialize, Debug)] 218 | struct CrateInfo { 219 | downloads: u64, 220 | } 221 | 222 | #[derive(Deserialize, Debug)] 223 | struct Crate { 224 | #[serde(rename = "crate")] 225 | info: CrateInfo, 226 | } 227 | 228 | async fn get_downloads(github_url: &str) -> Option { 229 | warn!("Downloading Crates downloads for {}", github_url); 230 | let rewritten = CRATE_REGEX 231 | .replace_all(&github_url, "https://crates.io/api/v1/crates/$crate") 232 | .to_string(); 233 | let req = CLIENT.get(&rewritten); 234 | 235 | let resp = req.send().await; 236 | match resp { 237 | Err(err) => { 238 | warn!("Error while getting {}: {}", github_url, err); 239 | return None; 240 | } 241 | Ok(ok) => { 242 | let data = ok.json::().await.unwrap(); 243 | return Some(data.info.downloads); 244 | } 245 | } 246 | } 247 | 248 | fn get_url_core(url: String) -> BoxFuture<'static, (String, Result<(), CheckerError>)> { 249 | async move { 250 | let mut res = Err(CheckerError::NotTried); 251 | for _ in 0..5u8 { 252 | debug!("Running {}", url); 253 | if env::var("USERNAME_FOR_GITHUB").is_ok() && env::var("TOKEN_FOR_GITHUB").is_ok() && GITHUB_REPO_REGEX.is_match(&url) { 254 | let rewritten = GITHUB_REPO_REGEX.replace_all(&url, "https://api.github.com/repos/$org/$repo"); 255 | info!("Replacing {} with {} to workaround rate limits on Github", url, rewritten); 256 | let (_new_url, res) = get_url_core(rewritten.to_string()).await; 257 | return (url, res); 258 | } 259 | let mut req = CLIENT 260 | .get(&url) 261 | .header(header::ACCEPT, "image/svg+xml, text/html, */*;q=0.8"); 262 | 263 | if GITHUB_API_REGEX.is_match(&url) { 264 | if let Ok(username) = env::var("USERNAME_FOR_GITHUB") { 265 | if let Ok(password) = env::var("TOKEN_FOR_GITHUB") { 266 | // needs a token with at least public_repo scope 267 | info!("Using basic auth for {}", url); 268 | req = req.basic_auth(username, Some(password)); 269 | } 270 | } 271 | } 272 | 273 | let resp = req.send().await; 274 | match resp { 275 | Err(err) => { 276 | warn!("Error while getting {}, retrying: {}", url, err); 277 | res = Err(CheckerError::ReqwestError{error: err.to_string()}); 278 | continue; 279 | } 280 | Ok(ok) => { 281 | let status = ok.status(); 282 | if status != StatusCode::OK { 283 | lazy_static! { 284 | static ref ACTIONS_REGEX: Regex = Regex::new(r"https://github.com/(?P[^/]+)/(?P[^/]+)/actions(?:\?workflow=.+)?").unwrap(); 285 | static ref YOUTUBE_VIDEO_REGEX: Regex = Regex::new(r"https://www.youtube.com/watch\?v=(?P.+)").unwrap(); 286 | static ref YOUTUBE_PLAYLIST_REGEX: Regex = Regex::new(r"https://www.youtube.com/playlist\?list=(?P.+)").unwrap(); 287 | static ref YOUTUBE_CONSENT_REGEX: Regex = Regex::new(r"https://consent.youtube.com/m\?continue=.+").unwrap(); 288 | static ref AZURE_BUILD_REGEX: Regex = Regex::new(r"https://dev.azure.com/[^/]+/[^/]+/_build").unwrap(); 289 | } 290 | if status == StatusCode::NOT_FOUND && ACTIONS_REGEX.is_match(&url) { 291 | let rewritten = ACTIONS_REGEX.replace_all(&url, "https://github.com/$org/$repo"); 292 | warn!("Got 404 with Github actions, so replacing {} with {}", url, rewritten); 293 | let (_new_url, res) = get_url_core(rewritten.to_string()).await; 294 | return (url, res); 295 | } 296 | if status == StatusCode::FOUND && YOUTUBE_VIDEO_REGEX.is_match(&url) { 297 | // Based off of https://gist.github.com/tonY1883/a3b85925081688de569b779b4657439b 298 | // Guesswork is that the img feed will cause less 302's than the main url 299 | // See https://github.com/rust-unofficial/awesome-rust/issues/814 for original issue 300 | let rewritten = YOUTUBE_VIDEO_REGEX.replace_all(&url, "http://img.youtube.com/vi/$video_id/mqdefault.jpg"); 301 | warn!("Got 302 with Youtube, so replacing {} with {}", url, rewritten); 302 | let (_new_url, res) = get_url_core(rewritten.to_string()).await; 303 | return (url, res); 304 | }; 305 | if status == StatusCode::FOUND && YOUTUBE_PLAYLIST_REGEX.is_match(&url) { 306 | let location = ok.headers().get("LOCATION").map(|h| h.to_str().unwrap()).unwrap_or_default(); 307 | if YOUTUBE_CONSENT_REGEX.is_match(location) { 308 | warn!("Got Youtube consent link for {}, so assuming playlist is ok", url); 309 | return (url, Ok(())); 310 | } 311 | }; 312 | if status == StatusCode::FOUND && AZURE_BUILD_REGEX.is_match(&url) { 313 | // Azure build urls always redirect to a particular build id, so no stable url guarantees 314 | let redirect = ok.headers().get(header::LOCATION).unwrap().to_str().unwrap(); 315 | let merged_url = Url::parse(&url).unwrap().join(redirect).unwrap(); 316 | info!("Got 302 from Azure devops, so replacing {} with {}", url, merged_url); 317 | let (_new_url, res) = get_url_core(merged_url.into_string()).await; 318 | return (url, res); 319 | } 320 | 321 | if status == StatusCode::TOO_MANY_REQUESTS { 322 | // We get a lot of these, and we should not retry as they'll just fail again 323 | warn!("Error while getting {}: {}", url, status); 324 | return (url, Err(CheckerError::TooManyRequests)); 325 | } 326 | 327 | if status.is_redirection() { 328 | if status != StatusCode::TEMPORARY_REDIRECT && status != StatusCode::FOUND { // ignore temporary redirects 329 | res = Err(CheckerError::HttpError {status: status.as_u16(), location: ok.headers().get(header::LOCATION).and_then(|h| h.to_str().ok()).map(|x| x.to_string())}); 330 | warn!("Redirect while getting {} - {}", url, status); 331 | break; 332 | } 333 | } else { 334 | warn!("Error while getting {}, retrying: {}", url, status); 335 | res = Err(CheckerError::HttpError {status: status.as_u16(), location: None}); 336 | continue; 337 | } 338 | } 339 | lazy_static! { 340 | static ref TRAVIS_IMG_REGEX: Regex = Regex::new(r"https://api.travis-ci.(?:com|org)/[^/]+/.+\.svg(\?.+)?").unwrap(); 341 | static ref GITHUB_ACTIONS_REGEX: Regex = Regex::new(r"https://github.com/[^/]+/[^/]+/workflows/[^/]+/badge.svg(\?.+)?").unwrap(); 342 | } 343 | if let Some(matches) = TRAVIS_IMG_REGEX.captures(&url) { 344 | // Previously we checked the Content-Disposition headers, but sometimes that is incorrect 345 | // We're now looking for the explicit text "unknown" in the middle of the SVG 346 | let content = ok.text().await.unwrap(); 347 | if content.contains("unknown") { 348 | res = Err(CheckerError::TravisBuildUnknown); 349 | break; 350 | } 351 | let query = matches.get(1).map(|x| x.as_str()).unwrap_or(""); 352 | if !query.starts_with("?") || query.find("branch=").is_none() { 353 | res = Err(CheckerError::TravisBuildNoBranch); 354 | break; 355 | } 356 | } 357 | debug!("Finished {}", url); 358 | res = Ok(()); 359 | break; 360 | } 361 | } 362 | } 363 | (url, res) 364 | }.boxed() 365 | } 366 | 367 | #[derive(Debug, Serialize, Deserialize)] 368 | enum Working { 369 | Yes, 370 | No(CheckerError), 371 | } 372 | 373 | #[derive(Debug, Serialize, Deserialize)] 374 | struct Link { 375 | last_working: Option>, 376 | updated_at: DateTime, 377 | working: Working, 378 | } 379 | 380 | type Results = BTreeMap; 381 | 382 | #[derive(Debug, Serialize, Deserialize)] 383 | struct PopularityData { 384 | pub github_stars: BTreeMap, 385 | pub cargo_downloads: BTreeMap, 386 | } 387 | 388 | #[tokio::main] 389 | async fn main() -> Result<(), Error> { 390 | env_logger::init(); 391 | let markdown_input = fs::read_to_string("README.md").expect("Can't read README.md"); 392 | let parser = Parser::new(&markdown_input); 393 | 394 | let mut used: BTreeSet = BTreeSet::new(); 395 | let mut results: Results = fs::read_to_string("results/results.yaml") 396 | .map_err(|e| format_err!("{}", e)) 397 | .and_then(|x| serde_yaml::from_str(&x).map_err(|e| format_err!("{}", e))) 398 | .unwrap_or(Results::new()); 399 | 400 | let mut popularity_data: PopularityData = fs::read_to_string("results/popularity.yaml") 401 | .map_err(|e| format_err!("{}", e)) 402 | .and_then(|x| serde_yaml::from_str(&x).map_err(|e| format_err!("{}", e))) 403 | .unwrap_or(PopularityData { 404 | github_stars: BTreeMap::new(), 405 | cargo_downloads: BTreeMap::new(), 406 | }); 407 | 408 | let mut url_checks = vec![]; 409 | 410 | let min_between_checks: Duration = Duration::days(3); 411 | let max_allowed_failed: Duration = Duration::days(7); 412 | let mut do_check = |url: String| { 413 | if !url.starts_with("http") { 414 | return; 415 | } 416 | if used.contains(&url) { 417 | return; 418 | } 419 | used.insert(url.clone()); 420 | if let Some(link) = results.get(&url) { 421 | if let Working::Yes = link.working { 422 | let since = Local::now() - link.updated_at; 423 | if since < min_between_checks { 424 | return; 425 | } 426 | } 427 | } 428 | let check = get_url(url).boxed(); 429 | url_checks.push(check); 430 | }; 431 | 432 | let mut to_check: Vec = vec![]; 433 | 434 | #[derive(Debug)] 435 | struct ListInfo { 436 | data: Vec, 437 | } 438 | 439 | let mut list_items: Vec = Vec::new(); 440 | let mut in_list_item = false; 441 | let mut list_item: String = String::new(); 442 | 443 | let mut link_count: u8 = 0; 444 | let mut github_stars: Option = None; 445 | let mut cargo_downloads: Option = None; 446 | 447 | let mut required_stars: u32 = MINIMUM_GITHUB_STARS; 448 | let mut last_level: u32 = 0; 449 | let mut star_override_level: Option = None; 450 | 451 | for (event, _range) in parser.into_offset_iter() { 452 | match event { 453 | Event::Start(tag) => { 454 | match tag { 455 | Tag::Link(_link_type, url, _title) | Tag::Image(_link_type, url, _title) => { 456 | if !url.starts_with("#") { 457 | let new_url = url.to_string(); 458 | if POPULARITY_OVERRIDES.contains(&new_url) { 459 | github_stars = Some(MINIMUM_GITHUB_STARS); 460 | } else if GITHUB_REPO_REGEX.is_match(&url) { 461 | let github_url = GITHUB_REPO_REGEX 462 | .replace_all(&url, "https://github.com/$org/$repo") 463 | .to_string(); 464 | let existing = popularity_data.github_stars.get(&github_url); 465 | if let Some(stars) = existing { 466 | // Use existing star data, but re-retrieve url to check aliveness 467 | // Some will have overrides, so don't check the regex yet 468 | github_stars = Some(*stars) 469 | } else { 470 | github_stars = get_stars(&github_url).await; 471 | if let Some(raw_stars) = github_stars { 472 | popularity_data 473 | .github_stars 474 | .insert(github_url.to_string(), raw_stars); 475 | if raw_stars >= required_stars { 476 | fs::write( 477 | "results/popularity.yaml", 478 | serde_yaml::to_string(&popularity_data)?, 479 | )?; 480 | } 481 | link_count += 1; 482 | continue; 483 | } 484 | } 485 | } 486 | if CRATE_REGEX.is_match(&url) { 487 | let existing = popularity_data.cargo_downloads.get(&new_url); 488 | if let Some(downloads) = existing { 489 | cargo_downloads = Some(*downloads); 490 | } else { 491 | let raw_downloads = get_downloads(&url).await; 492 | if let Some(positive_downloads) = raw_downloads { 493 | cargo_downloads = Some( 494 | positive_downloads.clamp(0, u32::MAX as u64) as u32, 495 | ); 496 | popularity_data 497 | .cargo_downloads 498 | .insert(new_url, cargo_downloads.unwrap()); 499 | if cargo_downloads.unwrap_or(0) >= MINIMUM_CARGO_DOWNLOADS { 500 | fs::write( 501 | "results/popularity.yaml", 502 | serde_yaml::to_string(&popularity_data)?, 503 | )?; 504 | } 505 | } 506 | link_count += 1; 507 | continue; 508 | } 509 | } 510 | 511 | to_check.push(url.to_string()); 512 | link_count += 1; 513 | } 514 | } 515 | Tag::List(_) => { 516 | if in_list_item && list_item.len() > 0 { 517 | list_items.last_mut().unwrap().data.push(list_item.clone()); 518 | in_list_item = false; 519 | } 520 | list_items.push(ListInfo { data: Vec::new() }); 521 | } 522 | Tag::Item => { 523 | if in_list_item && list_item.len() > 0 { 524 | list_items.last_mut().unwrap().data.push(list_item.clone()); 525 | } 526 | in_list_item = true; 527 | list_item = String::new(); 528 | link_count = 0; 529 | github_stars = None; 530 | cargo_downloads = None; 531 | } 532 | Tag::Heading(level) => { 533 | last_level = level; 534 | if let Some(override_level) = star_override_level { 535 | if level == override_level { 536 | star_override_level = None; 537 | required_stars = MINIMUM_GITHUB_STARS; 538 | } 539 | } 540 | } 541 | Tag::Paragraph => {} 542 | _ => { 543 | if in_list_item { 544 | in_list_item = false; 545 | } 546 | } 547 | } 548 | } 549 | Event::Text(text) => { 550 | let possible_override = override_stars(last_level, &text); 551 | if let Some(override_value) = possible_override { 552 | star_override_level = Some(last_level); 553 | required_stars = override_value; 554 | } 555 | 556 | if in_list_item { 557 | list_item.push_str(&text); 558 | } 559 | } 560 | Event::End(tag) => { 561 | match tag { 562 | Tag::Item => { 563 | if list_item.len() > 0 { 564 | if link_count > 0 { 565 | if github_stars.unwrap_or(0) < required_stars 566 | && cargo_downloads.unwrap_or(0) < MINIMUM_CARGO_DOWNLOADS 567 | { 568 | if github_stars.is_none() { 569 | warn!("No valid github link"); 570 | } 571 | if cargo_downloads.is_none() { 572 | warn!("No valid crates link"); 573 | } 574 | return Err(format_err!("Not high enough metrics ({:?} stars < {}, and {:?} cargo downloads < {}): {}", github_stars, required_stars, cargo_downloads, MINIMUM_CARGO_DOWNLOADS, list_item)); 575 | } 576 | } 577 | list_items.last_mut().unwrap().data.push(list_item.clone()); 578 | list_item = String::new(); 579 | } 580 | in_list_item = false 581 | } 582 | Tag::List(_) => { 583 | let list_info = list_items.pop().unwrap(); 584 | if list_info.data.iter().find(|s| *s == "License").is_some() 585 | && list_info.data.iter().find(|s| *s == "Resources").is_some() 586 | { 587 | // Ignore wrong ordering in top-level list 588 | continue; 589 | } 590 | let mut sorted_recent_list = list_info.data.to_vec(); 591 | sorted_recent_list.sort_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase())); 592 | let joined_recent = list_info.data.join("\n"); 593 | let joined_sorted = sorted_recent_list.join("\n"); 594 | let patch = create_patch(&joined_recent, &joined_sorted); 595 | if patch.hunks().len() > 0 { 596 | println!("{}", patch); 597 | return Err(format_err!("Sorting error")); 598 | } 599 | } 600 | _ => {} 601 | } 602 | } 603 | Event::Html(content) => { 604 | // Allow ToC markers, nothing else 605 | if !content.contains("