├── .github └── workflows │ ├── release-please.yml │ └── z-release.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE.md ├── README.md ├── examples ├── _quarto.yml ├── columbo_data.csv └── just-one-more-thing.qmd ├── justfile ├── notebook-embedded-json-cells-and-metadata.json └── src ├── main.rs └── observable_json.rs /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | name: release-please 6 | jobs: 7 | release-please: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: google-github-actions/release-please-action@v3 11 | with: 12 | release-type: rust 13 | package-name: release-please-action 14 | -------------------------------------------------------------------------------- /.github/workflows/z-release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | release: 3 | types: [created] 4 | 5 | jobs: 6 | release: 7 | name: release ${{ matrix.target }} (with non-required env) 8 | runs-on: ubuntu-latest 9 | if: github.event_name == 'release' 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | include: 14 | - target: x86_64-pc-windows-gnu 15 | archive: zip 16 | - target: x86_64-unknown-linux-musl 17 | archive: tar.gz 18 | - target: x86_64-apple-darwin 19 | archive: zip 20 | - target: wasm32-wasi 21 | archive: zip tar.gz 22 | steps: 23 | - uses: actions/checkout@master 24 | - name: Compile and release 25 | uses: rust-build/rust-build.action@master 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | with: 29 | RUSTTARGET: ${{ matrix.target }} 30 | EXTRA_FILES: "README.md" 31 | SRC_DIR: "src" 32 | ARCHIVE_TYPES: ${{ matrix.archive }} 33 | PRE_BUILD: "pre_build.sh" 34 | POST_BUILD: "test/post_build.sh" 35 | MINIFY: "yes" 36 | release_without_not_required: 37 | name: release ${{ matrix.target }} 38 | runs-on: ubuntu-latest 39 | strategy: 40 | fail-fast: true 41 | matrix: 42 | target: [x86_64-unknown-linux-musl] 43 | steps: 44 | - uses: actions/checkout@master 45 | - name: Compile and release 46 | uses: rust-build/rust-build.action@master 47 | with: 48 | RUSTTARGET: ${{ matrix.target }} 49 | UPLOAD_MODE: release 50 | - name: Checkout output 51 | run: ls -lR -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.2.0 (2022-08-20) 4 | 5 | 6 | ### Features 7 | 8 | * additional metadata in qmd yaml ([b2e1935](https://github.com/hrbrmstr/ohq2quarto/commit/b2e19352852724ffed6a577cd077eb577a350a33)) 9 | * create _quarto.yml with qmd ([b2e1935](https://github.com/hrbrmstr/ohq2quarto/commit/b2e19352852724ffed6a577cd077eb577a350a33)) 10 | * verbose option ([b2e1935](https://github.com/hrbrmstr/ohq2quarto/commit/b2e19352852724ffed6a577cd077eb577a350a33)) 11 | 12 | 13 | ### Miscellaneous Chores 14 | 15 | * release 0.2.0 ([78d83ad](https://github.com/hrbrmstr/ohq2quarto/commit/78d83ad316ff1890e0ab68cad4c8abc22f9387f1)) 16 | -------------------------------------------------------------------------------- /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 = "atty" 7 | version = "0.2.14" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 10 | dependencies = [ 11 | "hermit-abi", 12 | "libc", 13 | "winapi", 14 | ] 15 | 16 | [[package]] 17 | name = "autocfg" 18 | version = "1.1.0" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 21 | 22 | [[package]] 23 | name = "base64" 24 | version = "0.13.0" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 27 | 28 | [[package]] 29 | name = "bitflags" 30 | version = "1.3.2" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 33 | 34 | [[package]] 35 | name = "bumpalo" 36 | version = "3.11.0" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" 39 | 40 | [[package]] 41 | name = "byteorder" 42 | version = "1.4.3" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 45 | 46 | [[package]] 47 | name = "bytes" 48 | version = "1.2.1" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" 51 | 52 | [[package]] 53 | name = "cc" 54 | version = "1.0.73" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 57 | 58 | [[package]] 59 | name = "cfg-if" 60 | version = "1.0.0" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 63 | 64 | [[package]] 65 | name = "clap" 66 | version = "3.2.17" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "29e724a68d9319343bb3328c9cc2dfde263f4b3142ee1059a9980580171c954b" 69 | dependencies = [ 70 | "atty", 71 | "bitflags", 72 | "clap_derive", 73 | "clap_lex", 74 | "indexmap", 75 | "once_cell", 76 | "strsim", 77 | "termcolor", 78 | "textwrap", 79 | ] 80 | 81 | [[package]] 82 | name = "clap_derive" 83 | version = "3.2.17" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "13547f7012c01ab4a0e8f8967730ada8f9fdf419e8b6c792788f39cf4e46eefa" 86 | dependencies = [ 87 | "heck", 88 | "proc-macro-error", 89 | "proc-macro2", 90 | "quote", 91 | "syn", 92 | ] 93 | 94 | [[package]] 95 | name = "clap_lex" 96 | version = "0.2.4" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 99 | dependencies = [ 100 | "os_str_bytes", 101 | ] 102 | 103 | [[package]] 104 | name = "convert_case" 105 | version = "0.4.0" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 108 | 109 | [[package]] 110 | name = "core-foundation" 111 | version = "0.9.3" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 114 | dependencies = [ 115 | "core-foundation-sys", 116 | "libc", 117 | ] 118 | 119 | [[package]] 120 | name = "core-foundation-sys" 121 | version = "0.8.3" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 124 | 125 | [[package]] 126 | name = "cssparser" 127 | version = "0.27.2" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" 130 | dependencies = [ 131 | "cssparser-macros", 132 | "dtoa-short", 133 | "itoa 0.4.8", 134 | "matches", 135 | "phf 0.8.0", 136 | "proc-macro2", 137 | "quote", 138 | "smallvec", 139 | "syn", 140 | ] 141 | 142 | [[package]] 143 | name = "cssparser-macros" 144 | version = "0.6.0" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e" 147 | dependencies = [ 148 | "quote", 149 | "syn", 150 | ] 151 | 152 | [[package]] 153 | name = "derive_more" 154 | version = "0.99.17" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" 157 | dependencies = [ 158 | "convert_case", 159 | "proc-macro2", 160 | "quote", 161 | "rustc_version", 162 | "syn", 163 | ] 164 | 165 | [[package]] 166 | name = "dirs" 167 | version = "4.0.0" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" 170 | dependencies = [ 171 | "dirs-sys", 172 | ] 173 | 174 | [[package]] 175 | name = "dirs-sys" 176 | version = "0.3.7" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" 179 | dependencies = [ 180 | "libc", 181 | "redox_users", 182 | "winapi", 183 | ] 184 | 185 | [[package]] 186 | name = "downloader" 187 | version = "0.2.6" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "074093edfa907e8203c17c5111b04e114e03bde5ccdfa21a388fa4f34dabad96" 190 | dependencies = [ 191 | "futures", 192 | "rand 0.8.5", 193 | "reqwest", 194 | "thiserror", 195 | "tokio", 196 | ] 197 | 198 | [[package]] 199 | name = "dtoa" 200 | version = "0.4.8" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" 203 | 204 | [[package]] 205 | name = "dtoa-short" 206 | version = "0.3.3" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6" 209 | dependencies = [ 210 | "dtoa", 211 | ] 212 | 213 | [[package]] 214 | name = "ego-tree" 215 | version = "0.6.2" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "3a68a4904193147e0a8dec3314640e6db742afd5f6e634f428a6af230d9b3591" 218 | 219 | [[package]] 220 | name = "encoding_rs" 221 | version = "0.8.31" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" 224 | dependencies = [ 225 | "cfg-if", 226 | ] 227 | 228 | [[package]] 229 | name = "fastrand" 230 | version = "1.8.0" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" 233 | dependencies = [ 234 | "instant", 235 | ] 236 | 237 | [[package]] 238 | name = "fnv" 239 | version = "1.0.7" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 242 | 243 | [[package]] 244 | name = "foreign-types" 245 | version = "0.3.2" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 248 | dependencies = [ 249 | "foreign-types-shared", 250 | ] 251 | 252 | [[package]] 253 | name = "foreign-types-shared" 254 | version = "0.1.1" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 257 | 258 | [[package]] 259 | name = "form_urlencoded" 260 | version = "1.0.1" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 263 | dependencies = [ 264 | "matches", 265 | "percent-encoding", 266 | ] 267 | 268 | [[package]] 269 | name = "futf" 270 | version = "0.1.5" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" 273 | dependencies = [ 274 | "mac", 275 | "new_debug_unreachable", 276 | ] 277 | 278 | [[package]] 279 | name = "futures" 280 | version = "0.3.23" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "ab30e97ab6aacfe635fad58f22c2bb06c8b685f7421eb1e064a729e2a5f481fa" 283 | dependencies = [ 284 | "futures-channel", 285 | "futures-core", 286 | "futures-executor", 287 | "futures-io", 288 | "futures-sink", 289 | "futures-task", 290 | "futures-util", 291 | ] 292 | 293 | [[package]] 294 | name = "futures-channel" 295 | version = "0.3.23" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "2bfc52cbddcfd745bf1740338492bb0bd83d76c67b445f91c5fb29fae29ecaa1" 298 | dependencies = [ 299 | "futures-core", 300 | "futures-sink", 301 | ] 302 | 303 | [[package]] 304 | name = "futures-core" 305 | version = "0.3.23" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "d2acedae88d38235936c3922476b10fced7b2b68136f5e3c03c2d5be348a1115" 308 | 309 | [[package]] 310 | name = "futures-executor" 311 | version = "0.3.23" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "1d11aa21b5b587a64682c0094c2bdd4df0076c5324961a40cc3abd7f37930528" 314 | dependencies = [ 315 | "futures-core", 316 | "futures-task", 317 | "futures-util", 318 | ] 319 | 320 | [[package]] 321 | name = "futures-io" 322 | version = "0.3.23" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "93a66fc6d035a26a3ae255a6d2bca35eda63ae4c5512bef54449113f7a1228e5" 325 | 326 | [[package]] 327 | name = "futures-macro" 328 | version = "0.3.23" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "0db9cce532b0eae2ccf2766ab246f114b56b9cf6d445e00c2549fbc100ca045d" 331 | dependencies = [ 332 | "proc-macro2", 333 | "quote", 334 | "syn", 335 | ] 336 | 337 | [[package]] 338 | name = "futures-sink" 339 | version = "0.3.23" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "ca0bae1fe9752cf7fd9b0064c674ae63f97b37bc714d745cbde0afb7ec4e6765" 342 | 343 | [[package]] 344 | name = "futures-task" 345 | version = "0.3.23" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "842fc63b931f4056a24d59de13fb1272134ce261816e063e634ad0c15cdc5306" 348 | 349 | [[package]] 350 | name = "futures-util" 351 | version = "0.3.23" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "f0828a5471e340229c11c77ca80017937ce3c58cb788a17e5f1c2d5c485a9577" 354 | dependencies = [ 355 | "futures-channel", 356 | "futures-core", 357 | "futures-io", 358 | "futures-macro", 359 | "futures-sink", 360 | "futures-task", 361 | "memchr", 362 | "pin-project-lite", 363 | "pin-utils", 364 | "slab", 365 | ] 366 | 367 | [[package]] 368 | name = "fxhash" 369 | version = "0.2.1" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 372 | dependencies = [ 373 | "byteorder", 374 | ] 375 | 376 | [[package]] 377 | name = "getopts" 378 | version = "0.2.21" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" 381 | dependencies = [ 382 | "unicode-width", 383 | ] 384 | 385 | [[package]] 386 | name = "getrandom" 387 | version = "0.1.16" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" 390 | dependencies = [ 391 | "cfg-if", 392 | "libc", 393 | "wasi 0.9.0+wasi-snapshot-preview1", 394 | ] 395 | 396 | [[package]] 397 | name = "getrandom" 398 | version = "0.2.7" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" 401 | dependencies = [ 402 | "cfg-if", 403 | "libc", 404 | "wasi 0.11.0+wasi-snapshot-preview1", 405 | ] 406 | 407 | [[package]] 408 | name = "h2" 409 | version = "0.3.14" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" 412 | dependencies = [ 413 | "bytes", 414 | "fnv", 415 | "futures-core", 416 | "futures-sink", 417 | "futures-util", 418 | "http", 419 | "indexmap", 420 | "slab", 421 | "tokio", 422 | "tokio-util", 423 | "tracing", 424 | ] 425 | 426 | [[package]] 427 | name = "hashbrown" 428 | version = "0.12.3" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 431 | 432 | [[package]] 433 | name = "heck" 434 | version = "0.4.0" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 437 | 438 | [[package]] 439 | name = "hermit-abi" 440 | version = "0.1.19" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 443 | dependencies = [ 444 | "libc", 445 | ] 446 | 447 | [[package]] 448 | name = "html5ever" 449 | version = "0.26.0" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" 452 | dependencies = [ 453 | "log", 454 | "mac", 455 | "markup5ever", 456 | "proc-macro2", 457 | "quote", 458 | "syn", 459 | ] 460 | 461 | [[package]] 462 | name = "http" 463 | version = "0.2.8" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" 466 | dependencies = [ 467 | "bytes", 468 | "fnv", 469 | "itoa 1.0.3", 470 | ] 471 | 472 | [[package]] 473 | name = "http-body" 474 | version = "0.4.5" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" 477 | dependencies = [ 478 | "bytes", 479 | "http", 480 | "pin-project-lite", 481 | ] 482 | 483 | [[package]] 484 | name = "httparse" 485 | version = "1.7.1" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" 488 | 489 | [[package]] 490 | name = "httpdate" 491 | version = "1.0.2" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" 494 | 495 | [[package]] 496 | name = "hyper" 497 | version = "0.14.20" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" 500 | dependencies = [ 501 | "bytes", 502 | "futures-channel", 503 | "futures-core", 504 | "futures-util", 505 | "h2", 506 | "http", 507 | "http-body", 508 | "httparse", 509 | "httpdate", 510 | "itoa 1.0.3", 511 | "pin-project-lite", 512 | "socket2", 513 | "tokio", 514 | "tower-service", 515 | "tracing", 516 | "want", 517 | ] 518 | 519 | [[package]] 520 | name = "hyper-tls" 521 | version = "0.5.0" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" 524 | dependencies = [ 525 | "bytes", 526 | "hyper", 527 | "native-tls", 528 | "tokio", 529 | "tokio-native-tls", 530 | ] 531 | 532 | [[package]] 533 | name = "idna" 534 | version = "0.2.3" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 537 | dependencies = [ 538 | "matches", 539 | "unicode-bidi", 540 | "unicode-normalization", 541 | ] 542 | 543 | [[package]] 544 | name = "indexmap" 545 | version = "1.9.1" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" 548 | dependencies = [ 549 | "autocfg", 550 | "hashbrown", 551 | ] 552 | 553 | [[package]] 554 | name = "instant" 555 | version = "0.1.12" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 558 | dependencies = [ 559 | "cfg-if", 560 | ] 561 | 562 | [[package]] 563 | name = "ipnet" 564 | version = "2.5.0" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" 567 | 568 | [[package]] 569 | name = "itoa" 570 | version = "0.4.8" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 573 | 574 | [[package]] 575 | name = "itoa" 576 | version = "1.0.3" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" 579 | 580 | [[package]] 581 | name = "js-sys" 582 | version = "0.3.59" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" 585 | dependencies = [ 586 | "wasm-bindgen", 587 | ] 588 | 589 | [[package]] 590 | name = "lazy_static" 591 | version = "1.4.0" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 594 | 595 | [[package]] 596 | name = "libc" 597 | version = "0.2.132" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" 600 | 601 | [[package]] 602 | name = "lock_api" 603 | version = "0.4.7" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" 606 | dependencies = [ 607 | "autocfg", 608 | "scopeguard", 609 | ] 610 | 611 | [[package]] 612 | name = "log" 613 | version = "0.4.17" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 616 | dependencies = [ 617 | "cfg-if", 618 | ] 619 | 620 | [[package]] 621 | name = "mac" 622 | version = "0.1.1" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" 625 | 626 | [[package]] 627 | name = "markup5ever" 628 | version = "0.11.0" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" 631 | dependencies = [ 632 | "log", 633 | "phf 0.10.1", 634 | "phf_codegen 0.10.0", 635 | "string_cache", 636 | "string_cache_codegen", 637 | "tendril", 638 | ] 639 | 640 | [[package]] 641 | name = "matches" 642 | version = "0.1.9" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 645 | 646 | [[package]] 647 | name = "memchr" 648 | version = "2.5.0" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 651 | 652 | [[package]] 653 | name = "mime" 654 | version = "0.3.16" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 657 | 658 | [[package]] 659 | name = "mio" 660 | version = "0.8.4" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" 663 | dependencies = [ 664 | "libc", 665 | "log", 666 | "wasi 0.11.0+wasi-snapshot-preview1", 667 | "windows-sys", 668 | ] 669 | 670 | [[package]] 671 | name = "native-tls" 672 | version = "0.2.10" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" 675 | dependencies = [ 676 | "lazy_static", 677 | "libc", 678 | "log", 679 | "openssl", 680 | "openssl-probe", 681 | "openssl-sys", 682 | "schannel", 683 | "security-framework", 684 | "security-framework-sys", 685 | "tempfile", 686 | ] 687 | 688 | [[package]] 689 | name = "new_debug_unreachable" 690 | version = "1.0.4" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" 693 | 694 | [[package]] 695 | name = "nodrop" 696 | version = "0.1.14" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" 699 | 700 | [[package]] 701 | name = "num_cpus" 702 | version = "1.13.1" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 705 | dependencies = [ 706 | "hermit-abi", 707 | "libc", 708 | ] 709 | 710 | [[package]] 711 | name = "ohq2quarto" 712 | version = "0.2.1" 713 | dependencies = [ 714 | "clap", 715 | "downloader", 716 | "reqwest", 717 | "scraper", 718 | "serde", 719 | "serde_derive", 720 | "serde_json", 721 | "shellexpand", 722 | ] 723 | 724 | [[package]] 725 | name = "once_cell" 726 | version = "1.13.1" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" 729 | 730 | [[package]] 731 | name = "openssl" 732 | version = "0.10.41" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0" 735 | dependencies = [ 736 | "bitflags", 737 | "cfg-if", 738 | "foreign-types", 739 | "libc", 740 | "once_cell", 741 | "openssl-macros", 742 | "openssl-sys", 743 | ] 744 | 745 | [[package]] 746 | name = "openssl-macros" 747 | version = "0.1.0" 748 | source = "registry+https://github.com/rust-lang/crates.io-index" 749 | checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" 750 | dependencies = [ 751 | "proc-macro2", 752 | "quote", 753 | "syn", 754 | ] 755 | 756 | [[package]] 757 | name = "openssl-probe" 758 | version = "0.1.5" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 761 | 762 | [[package]] 763 | name = "openssl-sys" 764 | version = "0.9.75" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" 767 | dependencies = [ 768 | "autocfg", 769 | "cc", 770 | "libc", 771 | "pkg-config", 772 | "vcpkg", 773 | ] 774 | 775 | [[package]] 776 | name = "os_str_bytes" 777 | version = "6.3.0" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" 780 | 781 | [[package]] 782 | name = "parking_lot" 783 | version = "0.12.1" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 786 | dependencies = [ 787 | "lock_api", 788 | "parking_lot_core", 789 | ] 790 | 791 | [[package]] 792 | name = "parking_lot_core" 793 | version = "0.9.3" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" 796 | dependencies = [ 797 | "cfg-if", 798 | "libc", 799 | "redox_syscall", 800 | "smallvec", 801 | "windows-sys", 802 | ] 803 | 804 | [[package]] 805 | name = "percent-encoding" 806 | version = "2.1.0" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 809 | 810 | [[package]] 811 | name = "phf" 812 | version = "0.8.0" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" 815 | dependencies = [ 816 | "phf_macros", 817 | "phf_shared 0.8.0", 818 | "proc-macro-hack", 819 | ] 820 | 821 | [[package]] 822 | name = "phf" 823 | version = "0.10.1" 824 | source = "registry+https://github.com/rust-lang/crates.io-index" 825 | checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" 826 | dependencies = [ 827 | "phf_shared 0.10.0", 828 | ] 829 | 830 | [[package]] 831 | name = "phf_codegen" 832 | version = "0.8.0" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" 835 | dependencies = [ 836 | "phf_generator 0.8.0", 837 | "phf_shared 0.8.0", 838 | ] 839 | 840 | [[package]] 841 | name = "phf_codegen" 842 | version = "0.10.0" 843 | source = "registry+https://github.com/rust-lang/crates.io-index" 844 | checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" 845 | dependencies = [ 846 | "phf_generator 0.10.0", 847 | "phf_shared 0.10.0", 848 | ] 849 | 850 | [[package]] 851 | name = "phf_generator" 852 | version = "0.8.0" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" 855 | dependencies = [ 856 | "phf_shared 0.8.0", 857 | "rand 0.7.3", 858 | ] 859 | 860 | [[package]] 861 | name = "phf_generator" 862 | version = "0.10.0" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" 865 | dependencies = [ 866 | "phf_shared 0.10.0", 867 | "rand 0.8.5", 868 | ] 869 | 870 | [[package]] 871 | name = "phf_macros" 872 | version = "0.8.0" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" 875 | dependencies = [ 876 | "phf_generator 0.8.0", 877 | "phf_shared 0.8.0", 878 | "proc-macro-hack", 879 | "proc-macro2", 880 | "quote", 881 | "syn", 882 | ] 883 | 884 | [[package]] 885 | name = "phf_shared" 886 | version = "0.8.0" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" 889 | dependencies = [ 890 | "siphasher", 891 | ] 892 | 893 | [[package]] 894 | name = "phf_shared" 895 | version = "0.10.0" 896 | source = "registry+https://github.com/rust-lang/crates.io-index" 897 | checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" 898 | dependencies = [ 899 | "siphasher", 900 | ] 901 | 902 | [[package]] 903 | name = "pin-project-lite" 904 | version = "0.2.9" 905 | source = "registry+https://github.com/rust-lang/crates.io-index" 906 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 907 | 908 | [[package]] 909 | name = "pin-utils" 910 | version = "0.1.0" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 913 | 914 | [[package]] 915 | name = "pkg-config" 916 | version = "0.3.25" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" 919 | 920 | [[package]] 921 | name = "ppv-lite86" 922 | version = "0.2.16" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" 925 | 926 | [[package]] 927 | name = "precomputed-hash" 928 | version = "0.1.1" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 931 | 932 | [[package]] 933 | name = "proc-macro-error" 934 | version = "1.0.4" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 937 | dependencies = [ 938 | "proc-macro-error-attr", 939 | "proc-macro2", 940 | "quote", 941 | "syn", 942 | "version_check", 943 | ] 944 | 945 | [[package]] 946 | name = "proc-macro-error-attr" 947 | version = "1.0.4" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 950 | dependencies = [ 951 | "proc-macro2", 952 | "quote", 953 | "version_check", 954 | ] 955 | 956 | [[package]] 957 | name = "proc-macro-hack" 958 | version = "0.5.19" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 961 | 962 | [[package]] 963 | name = "proc-macro2" 964 | version = "1.0.43" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" 967 | dependencies = [ 968 | "unicode-ident", 969 | ] 970 | 971 | [[package]] 972 | name = "quote" 973 | version = "1.0.21" 974 | source = "registry+https://github.com/rust-lang/crates.io-index" 975 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 976 | dependencies = [ 977 | "proc-macro2", 978 | ] 979 | 980 | [[package]] 981 | name = "rand" 982 | version = "0.7.3" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 985 | dependencies = [ 986 | "getrandom 0.1.16", 987 | "libc", 988 | "rand_chacha 0.2.2", 989 | "rand_core 0.5.1", 990 | "rand_hc", 991 | "rand_pcg", 992 | ] 993 | 994 | [[package]] 995 | name = "rand" 996 | version = "0.8.5" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 999 | dependencies = [ 1000 | "libc", 1001 | "rand_chacha 0.3.1", 1002 | "rand_core 0.6.3", 1003 | ] 1004 | 1005 | [[package]] 1006 | name = "rand_chacha" 1007 | version = "0.2.2" 1008 | source = "registry+https://github.com/rust-lang/crates.io-index" 1009 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 1010 | dependencies = [ 1011 | "ppv-lite86", 1012 | "rand_core 0.5.1", 1013 | ] 1014 | 1015 | [[package]] 1016 | name = "rand_chacha" 1017 | version = "0.3.1" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1020 | dependencies = [ 1021 | "ppv-lite86", 1022 | "rand_core 0.6.3", 1023 | ] 1024 | 1025 | [[package]] 1026 | name = "rand_core" 1027 | version = "0.5.1" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 1030 | dependencies = [ 1031 | "getrandom 0.1.16", 1032 | ] 1033 | 1034 | [[package]] 1035 | name = "rand_core" 1036 | version = "0.6.3" 1037 | source = "registry+https://github.com/rust-lang/crates.io-index" 1038 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 1039 | dependencies = [ 1040 | "getrandom 0.2.7", 1041 | ] 1042 | 1043 | [[package]] 1044 | name = "rand_hc" 1045 | version = "0.2.0" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 1048 | dependencies = [ 1049 | "rand_core 0.5.1", 1050 | ] 1051 | 1052 | [[package]] 1053 | name = "rand_pcg" 1054 | version = "0.2.1" 1055 | source = "registry+https://github.com/rust-lang/crates.io-index" 1056 | checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" 1057 | dependencies = [ 1058 | "rand_core 0.5.1", 1059 | ] 1060 | 1061 | [[package]] 1062 | name = "redox_syscall" 1063 | version = "0.2.16" 1064 | source = "registry+https://github.com/rust-lang/crates.io-index" 1065 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 1066 | dependencies = [ 1067 | "bitflags", 1068 | ] 1069 | 1070 | [[package]] 1071 | name = "redox_users" 1072 | version = "0.4.3" 1073 | source = "registry+https://github.com/rust-lang/crates.io-index" 1074 | checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" 1075 | dependencies = [ 1076 | "getrandom 0.2.7", 1077 | "redox_syscall", 1078 | "thiserror", 1079 | ] 1080 | 1081 | [[package]] 1082 | name = "remove_dir_all" 1083 | version = "0.5.3" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 1086 | dependencies = [ 1087 | "winapi", 1088 | ] 1089 | 1090 | [[package]] 1091 | name = "reqwest" 1092 | version = "0.11.11" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" 1095 | dependencies = [ 1096 | "base64", 1097 | "bytes", 1098 | "encoding_rs", 1099 | "futures-core", 1100 | "futures-util", 1101 | "h2", 1102 | "http", 1103 | "http-body", 1104 | "hyper", 1105 | "hyper-tls", 1106 | "ipnet", 1107 | "js-sys", 1108 | "lazy_static", 1109 | "log", 1110 | "mime", 1111 | "native-tls", 1112 | "percent-encoding", 1113 | "pin-project-lite", 1114 | "serde", 1115 | "serde_json", 1116 | "serde_urlencoded", 1117 | "tokio", 1118 | "tokio-native-tls", 1119 | "tower-service", 1120 | "url", 1121 | "wasm-bindgen", 1122 | "wasm-bindgen-futures", 1123 | "web-sys", 1124 | "winreg", 1125 | ] 1126 | 1127 | [[package]] 1128 | name = "rustc_version" 1129 | version = "0.4.0" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 1132 | dependencies = [ 1133 | "semver", 1134 | ] 1135 | 1136 | [[package]] 1137 | name = "ryu" 1138 | version = "1.0.11" 1139 | source = "registry+https://github.com/rust-lang/crates.io-index" 1140 | checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" 1141 | 1142 | [[package]] 1143 | name = "schannel" 1144 | version = "0.1.20" 1145 | source = "registry+https://github.com/rust-lang/crates.io-index" 1146 | checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" 1147 | dependencies = [ 1148 | "lazy_static", 1149 | "windows-sys", 1150 | ] 1151 | 1152 | [[package]] 1153 | name = "scopeguard" 1154 | version = "1.1.0" 1155 | source = "registry+https://github.com/rust-lang/crates.io-index" 1156 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1157 | 1158 | [[package]] 1159 | name = "scraper" 1160 | version = "0.13.0" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "5684396b456f3eb69ceeb34d1b5cb1a2f6acf7ca4452131efa3ba0ee2c2d0a70" 1163 | dependencies = [ 1164 | "cssparser", 1165 | "ego-tree", 1166 | "getopts", 1167 | "html5ever", 1168 | "matches", 1169 | "selectors", 1170 | "smallvec", 1171 | "tendril", 1172 | ] 1173 | 1174 | [[package]] 1175 | name = "security-framework" 1176 | version = "2.7.0" 1177 | source = "registry+https://github.com/rust-lang/crates.io-index" 1178 | checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" 1179 | dependencies = [ 1180 | "bitflags", 1181 | "core-foundation", 1182 | "core-foundation-sys", 1183 | "libc", 1184 | "security-framework-sys", 1185 | ] 1186 | 1187 | [[package]] 1188 | name = "security-framework-sys" 1189 | version = "2.6.1" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" 1192 | dependencies = [ 1193 | "core-foundation-sys", 1194 | "libc", 1195 | ] 1196 | 1197 | [[package]] 1198 | name = "selectors" 1199 | version = "0.22.0" 1200 | source = "registry+https://github.com/rust-lang/crates.io-index" 1201 | checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" 1202 | dependencies = [ 1203 | "bitflags", 1204 | "cssparser", 1205 | "derive_more", 1206 | "fxhash", 1207 | "log", 1208 | "matches", 1209 | "phf 0.8.0", 1210 | "phf_codegen 0.8.0", 1211 | "precomputed-hash", 1212 | "servo_arc", 1213 | "smallvec", 1214 | "thin-slice", 1215 | ] 1216 | 1217 | [[package]] 1218 | name = "semver" 1219 | version = "1.0.13" 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" 1221 | checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" 1222 | 1223 | [[package]] 1224 | name = "serde" 1225 | version = "1.0.143" 1226 | source = "registry+https://github.com/rust-lang/crates.io-index" 1227 | checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553" 1228 | 1229 | [[package]] 1230 | name = "serde_derive" 1231 | version = "1.0.143" 1232 | source = "registry+https://github.com/rust-lang/crates.io-index" 1233 | checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391" 1234 | dependencies = [ 1235 | "proc-macro2", 1236 | "quote", 1237 | "syn", 1238 | ] 1239 | 1240 | [[package]] 1241 | name = "serde_json" 1242 | version = "1.0.83" 1243 | source = "registry+https://github.com/rust-lang/crates.io-index" 1244 | checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" 1245 | dependencies = [ 1246 | "itoa 1.0.3", 1247 | "ryu", 1248 | "serde", 1249 | ] 1250 | 1251 | [[package]] 1252 | name = "serde_urlencoded" 1253 | version = "0.7.1" 1254 | source = "registry+https://github.com/rust-lang/crates.io-index" 1255 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1256 | dependencies = [ 1257 | "form_urlencoded", 1258 | "itoa 1.0.3", 1259 | "ryu", 1260 | "serde", 1261 | ] 1262 | 1263 | [[package]] 1264 | name = "servo_arc" 1265 | version = "0.1.1" 1266 | source = "registry+https://github.com/rust-lang/crates.io-index" 1267 | checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" 1268 | dependencies = [ 1269 | "nodrop", 1270 | "stable_deref_trait", 1271 | ] 1272 | 1273 | [[package]] 1274 | name = "shellexpand" 1275 | version = "2.1.2" 1276 | source = "registry+https://github.com/rust-lang/crates.io-index" 1277 | checksum = "7ccc8076840c4da029af4f87e4e8daeb0fca6b87bbb02e10cb60b791450e11e4" 1278 | dependencies = [ 1279 | "dirs", 1280 | ] 1281 | 1282 | [[package]] 1283 | name = "siphasher" 1284 | version = "0.3.10" 1285 | source = "registry+https://github.com/rust-lang/crates.io-index" 1286 | checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" 1287 | 1288 | [[package]] 1289 | name = "slab" 1290 | version = "0.4.7" 1291 | source = "registry+https://github.com/rust-lang/crates.io-index" 1292 | checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" 1293 | dependencies = [ 1294 | "autocfg", 1295 | ] 1296 | 1297 | [[package]] 1298 | name = "smallvec" 1299 | version = "1.9.0" 1300 | source = "registry+https://github.com/rust-lang/crates.io-index" 1301 | checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" 1302 | 1303 | [[package]] 1304 | name = "socket2" 1305 | version = "0.4.4" 1306 | source = "registry+https://github.com/rust-lang/crates.io-index" 1307 | checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" 1308 | dependencies = [ 1309 | "libc", 1310 | "winapi", 1311 | ] 1312 | 1313 | [[package]] 1314 | name = "stable_deref_trait" 1315 | version = "1.2.0" 1316 | source = "registry+https://github.com/rust-lang/crates.io-index" 1317 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1318 | 1319 | [[package]] 1320 | name = "string_cache" 1321 | version = "0.8.4" 1322 | source = "registry+https://github.com/rust-lang/crates.io-index" 1323 | checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08" 1324 | dependencies = [ 1325 | "new_debug_unreachable", 1326 | "once_cell", 1327 | "parking_lot", 1328 | "phf_shared 0.10.0", 1329 | "precomputed-hash", 1330 | "serde", 1331 | ] 1332 | 1333 | [[package]] 1334 | name = "string_cache_codegen" 1335 | version = "0.5.2" 1336 | source = "registry+https://github.com/rust-lang/crates.io-index" 1337 | checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" 1338 | dependencies = [ 1339 | "phf_generator 0.10.0", 1340 | "phf_shared 0.10.0", 1341 | "proc-macro2", 1342 | "quote", 1343 | ] 1344 | 1345 | [[package]] 1346 | name = "strsim" 1347 | version = "0.10.0" 1348 | source = "registry+https://github.com/rust-lang/crates.io-index" 1349 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1350 | 1351 | [[package]] 1352 | name = "syn" 1353 | version = "1.0.99" 1354 | source = "registry+https://github.com/rust-lang/crates.io-index" 1355 | checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" 1356 | dependencies = [ 1357 | "proc-macro2", 1358 | "quote", 1359 | "unicode-ident", 1360 | ] 1361 | 1362 | [[package]] 1363 | name = "tempfile" 1364 | version = "3.3.0" 1365 | source = "registry+https://github.com/rust-lang/crates.io-index" 1366 | checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" 1367 | dependencies = [ 1368 | "cfg-if", 1369 | "fastrand", 1370 | "libc", 1371 | "redox_syscall", 1372 | "remove_dir_all", 1373 | "winapi", 1374 | ] 1375 | 1376 | [[package]] 1377 | name = "tendril" 1378 | version = "0.4.3" 1379 | source = "registry+https://github.com/rust-lang/crates.io-index" 1380 | checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" 1381 | dependencies = [ 1382 | "futf", 1383 | "mac", 1384 | "utf-8", 1385 | ] 1386 | 1387 | [[package]] 1388 | name = "termcolor" 1389 | version = "1.1.3" 1390 | source = "registry+https://github.com/rust-lang/crates.io-index" 1391 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 1392 | dependencies = [ 1393 | "winapi-util", 1394 | ] 1395 | 1396 | [[package]] 1397 | name = "textwrap" 1398 | version = "0.15.0" 1399 | source = "registry+https://github.com/rust-lang/crates.io-index" 1400 | checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" 1401 | 1402 | [[package]] 1403 | name = "thin-slice" 1404 | version = "0.1.1" 1405 | source = "registry+https://github.com/rust-lang/crates.io-index" 1406 | checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" 1407 | 1408 | [[package]] 1409 | name = "thiserror" 1410 | version = "1.0.32" 1411 | source = "registry+https://github.com/rust-lang/crates.io-index" 1412 | checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" 1413 | dependencies = [ 1414 | "thiserror-impl", 1415 | ] 1416 | 1417 | [[package]] 1418 | name = "thiserror-impl" 1419 | version = "1.0.32" 1420 | source = "registry+https://github.com/rust-lang/crates.io-index" 1421 | checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" 1422 | dependencies = [ 1423 | "proc-macro2", 1424 | "quote", 1425 | "syn", 1426 | ] 1427 | 1428 | [[package]] 1429 | name = "tinyvec" 1430 | version = "1.6.0" 1431 | source = "registry+https://github.com/rust-lang/crates.io-index" 1432 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1433 | dependencies = [ 1434 | "tinyvec_macros", 1435 | ] 1436 | 1437 | [[package]] 1438 | name = "tinyvec_macros" 1439 | version = "0.1.0" 1440 | source = "registry+https://github.com/rust-lang/crates.io-index" 1441 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1442 | 1443 | [[package]] 1444 | name = "tokio" 1445 | version = "1.20.1" 1446 | source = "registry+https://github.com/rust-lang/crates.io-index" 1447 | checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" 1448 | dependencies = [ 1449 | "autocfg", 1450 | "bytes", 1451 | "libc", 1452 | "memchr", 1453 | "mio", 1454 | "num_cpus", 1455 | "once_cell", 1456 | "pin-project-lite", 1457 | "socket2", 1458 | "winapi", 1459 | ] 1460 | 1461 | [[package]] 1462 | name = "tokio-native-tls" 1463 | version = "0.3.0" 1464 | source = "registry+https://github.com/rust-lang/crates.io-index" 1465 | checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" 1466 | dependencies = [ 1467 | "native-tls", 1468 | "tokio", 1469 | ] 1470 | 1471 | [[package]] 1472 | name = "tokio-util" 1473 | version = "0.7.3" 1474 | source = "registry+https://github.com/rust-lang/crates.io-index" 1475 | checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" 1476 | dependencies = [ 1477 | "bytes", 1478 | "futures-core", 1479 | "futures-sink", 1480 | "pin-project-lite", 1481 | "tokio", 1482 | "tracing", 1483 | ] 1484 | 1485 | [[package]] 1486 | name = "tower-service" 1487 | version = "0.3.2" 1488 | source = "registry+https://github.com/rust-lang/crates.io-index" 1489 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 1490 | 1491 | [[package]] 1492 | name = "tracing" 1493 | version = "0.1.36" 1494 | source = "registry+https://github.com/rust-lang/crates.io-index" 1495 | checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" 1496 | dependencies = [ 1497 | "cfg-if", 1498 | "pin-project-lite", 1499 | "tracing-core", 1500 | ] 1501 | 1502 | [[package]] 1503 | name = "tracing-core" 1504 | version = "0.1.29" 1505 | source = "registry+https://github.com/rust-lang/crates.io-index" 1506 | checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" 1507 | dependencies = [ 1508 | "once_cell", 1509 | ] 1510 | 1511 | [[package]] 1512 | name = "try-lock" 1513 | version = "0.2.3" 1514 | source = "registry+https://github.com/rust-lang/crates.io-index" 1515 | checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" 1516 | 1517 | [[package]] 1518 | name = "unicode-bidi" 1519 | version = "0.3.8" 1520 | source = "registry+https://github.com/rust-lang/crates.io-index" 1521 | checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" 1522 | 1523 | [[package]] 1524 | name = "unicode-ident" 1525 | version = "1.0.3" 1526 | source = "registry+https://github.com/rust-lang/crates.io-index" 1527 | checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" 1528 | 1529 | [[package]] 1530 | name = "unicode-normalization" 1531 | version = "0.1.21" 1532 | source = "registry+https://github.com/rust-lang/crates.io-index" 1533 | checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" 1534 | dependencies = [ 1535 | "tinyvec", 1536 | ] 1537 | 1538 | [[package]] 1539 | name = "unicode-width" 1540 | version = "0.1.9" 1541 | source = "registry+https://github.com/rust-lang/crates.io-index" 1542 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 1543 | 1544 | [[package]] 1545 | name = "url" 1546 | version = "2.2.2" 1547 | source = "registry+https://github.com/rust-lang/crates.io-index" 1548 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 1549 | dependencies = [ 1550 | "form_urlencoded", 1551 | "idna", 1552 | "matches", 1553 | "percent-encoding", 1554 | ] 1555 | 1556 | [[package]] 1557 | name = "utf-8" 1558 | version = "0.7.6" 1559 | source = "registry+https://github.com/rust-lang/crates.io-index" 1560 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 1561 | 1562 | [[package]] 1563 | name = "vcpkg" 1564 | version = "0.2.15" 1565 | source = "registry+https://github.com/rust-lang/crates.io-index" 1566 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1567 | 1568 | [[package]] 1569 | name = "version_check" 1570 | version = "0.9.4" 1571 | source = "registry+https://github.com/rust-lang/crates.io-index" 1572 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1573 | 1574 | [[package]] 1575 | name = "want" 1576 | version = "0.3.0" 1577 | source = "registry+https://github.com/rust-lang/crates.io-index" 1578 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 1579 | dependencies = [ 1580 | "log", 1581 | "try-lock", 1582 | ] 1583 | 1584 | [[package]] 1585 | name = "wasi" 1586 | version = "0.9.0+wasi-snapshot-preview1" 1587 | source = "registry+https://github.com/rust-lang/crates.io-index" 1588 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 1589 | 1590 | [[package]] 1591 | name = "wasi" 1592 | version = "0.11.0+wasi-snapshot-preview1" 1593 | source = "registry+https://github.com/rust-lang/crates.io-index" 1594 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1595 | 1596 | [[package]] 1597 | name = "wasm-bindgen" 1598 | version = "0.2.82" 1599 | source = "registry+https://github.com/rust-lang/crates.io-index" 1600 | checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" 1601 | dependencies = [ 1602 | "cfg-if", 1603 | "wasm-bindgen-macro", 1604 | ] 1605 | 1606 | [[package]] 1607 | name = "wasm-bindgen-backend" 1608 | version = "0.2.82" 1609 | source = "registry+https://github.com/rust-lang/crates.io-index" 1610 | checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" 1611 | dependencies = [ 1612 | "bumpalo", 1613 | "log", 1614 | "once_cell", 1615 | "proc-macro2", 1616 | "quote", 1617 | "syn", 1618 | "wasm-bindgen-shared", 1619 | ] 1620 | 1621 | [[package]] 1622 | name = "wasm-bindgen-futures" 1623 | version = "0.4.32" 1624 | source = "registry+https://github.com/rust-lang/crates.io-index" 1625 | checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" 1626 | dependencies = [ 1627 | "cfg-if", 1628 | "js-sys", 1629 | "wasm-bindgen", 1630 | "web-sys", 1631 | ] 1632 | 1633 | [[package]] 1634 | name = "wasm-bindgen-macro" 1635 | version = "0.2.82" 1636 | source = "registry+https://github.com/rust-lang/crates.io-index" 1637 | checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" 1638 | dependencies = [ 1639 | "quote", 1640 | "wasm-bindgen-macro-support", 1641 | ] 1642 | 1643 | [[package]] 1644 | name = "wasm-bindgen-macro-support" 1645 | version = "0.2.82" 1646 | source = "registry+https://github.com/rust-lang/crates.io-index" 1647 | checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" 1648 | dependencies = [ 1649 | "proc-macro2", 1650 | "quote", 1651 | "syn", 1652 | "wasm-bindgen-backend", 1653 | "wasm-bindgen-shared", 1654 | ] 1655 | 1656 | [[package]] 1657 | name = "wasm-bindgen-shared" 1658 | version = "0.2.82" 1659 | source = "registry+https://github.com/rust-lang/crates.io-index" 1660 | checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" 1661 | 1662 | [[package]] 1663 | name = "web-sys" 1664 | version = "0.3.59" 1665 | source = "registry+https://github.com/rust-lang/crates.io-index" 1666 | checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" 1667 | dependencies = [ 1668 | "js-sys", 1669 | "wasm-bindgen", 1670 | ] 1671 | 1672 | [[package]] 1673 | name = "winapi" 1674 | version = "0.3.9" 1675 | source = "registry+https://github.com/rust-lang/crates.io-index" 1676 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1677 | dependencies = [ 1678 | "winapi-i686-pc-windows-gnu", 1679 | "winapi-x86_64-pc-windows-gnu", 1680 | ] 1681 | 1682 | [[package]] 1683 | name = "winapi-i686-pc-windows-gnu" 1684 | version = "0.4.0" 1685 | source = "registry+https://github.com/rust-lang/crates.io-index" 1686 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1687 | 1688 | [[package]] 1689 | name = "winapi-util" 1690 | version = "0.1.5" 1691 | source = "registry+https://github.com/rust-lang/crates.io-index" 1692 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1693 | dependencies = [ 1694 | "winapi", 1695 | ] 1696 | 1697 | [[package]] 1698 | name = "winapi-x86_64-pc-windows-gnu" 1699 | version = "0.4.0" 1700 | source = "registry+https://github.com/rust-lang/crates.io-index" 1701 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1702 | 1703 | [[package]] 1704 | name = "windows-sys" 1705 | version = "0.36.1" 1706 | source = "registry+https://github.com/rust-lang/crates.io-index" 1707 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 1708 | dependencies = [ 1709 | "windows_aarch64_msvc", 1710 | "windows_i686_gnu", 1711 | "windows_i686_msvc", 1712 | "windows_x86_64_gnu", 1713 | "windows_x86_64_msvc", 1714 | ] 1715 | 1716 | [[package]] 1717 | name = "windows_aarch64_msvc" 1718 | version = "0.36.1" 1719 | source = "registry+https://github.com/rust-lang/crates.io-index" 1720 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 1721 | 1722 | [[package]] 1723 | name = "windows_i686_gnu" 1724 | version = "0.36.1" 1725 | source = "registry+https://github.com/rust-lang/crates.io-index" 1726 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 1727 | 1728 | [[package]] 1729 | name = "windows_i686_msvc" 1730 | version = "0.36.1" 1731 | source = "registry+https://github.com/rust-lang/crates.io-index" 1732 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 1733 | 1734 | [[package]] 1735 | name = "windows_x86_64_gnu" 1736 | version = "0.36.1" 1737 | source = "registry+https://github.com/rust-lang/crates.io-index" 1738 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 1739 | 1740 | [[package]] 1741 | name = "windows_x86_64_msvc" 1742 | version = "0.36.1" 1743 | source = "registry+https://github.com/rust-lang/crates.io-index" 1744 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 1745 | 1746 | [[package]] 1747 | name = "winreg" 1748 | version = "0.10.1" 1749 | source = "registry+https://github.com/rust-lang/crates.io-index" 1750 | checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" 1751 | dependencies = [ 1752 | "winapi", 1753 | ] 1754 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ohq2quarto" 3 | description = "Given an Observable Notebook reference, create a Quarto project with all FileAttachments" 4 | license = "MIT" 5 | authors = [ "boB Rudis (@hrbrmstr)" ] 6 | homepage = "https://github.com/hrbrmstr/ohq2quarto" 7 | version = "0.2.1" 8 | edition = "2021" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | reqwest = { version = "0.11.11", features = [ "blocking" ] } 14 | scraper = "0.13.0" 15 | serde = "1.0.137" 16 | serde_json = "1.0.81" 17 | serde_derive = "1.0.137" 18 | clap = { version = "3.1.18", features = ["derive"] } 19 | shellexpand = "2.1.2" 20 | downloader = "0.2.6" -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Bob Rudis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ohq2quarto 2 | 3 | Save an [Observable HQ](https://observablehq.com) Notebook to a [Quarto](https://quarto.org/) project. 4 | 5 | Given an Observable Notebook reference (full URL or `@user/slug`) and an output directory, this utility will make a Quarto project directory, build `qmd` & `_quarto.yml` files and download all `FileAttachment`s. 6 | 7 | ## TODO 8 | 9 | - [ ] [Handle Collections](https://github.com/hrbrmstr/ohq2quarto/issues/2) 10 | - [ ] [Make this a lib+bin](https://github.com/hrbrmstr/ohq2quarto/issues/3) 11 | - [ ] [Publish on crates.io](https://github.com/hrbrmstr/ohq2quarto/issues/4) 12 | 13 | ## Getting `ohq2quarto` 14 | 15 | The [releases](https://github.com/hrbrmstr/ohq2quarto/releases) section has pre-built binaries for Linux (x86\_64 & arm64), Windows and macOS (which is also a signed universal binary). 16 | 17 | DIY folks can: 18 | 19 | ```shell 20 | $ cargo install --git https://github.com/hrbrmstr/ohq2quarto --branch batman # install it (~/.cargo/bin/ohq2quarto) 21 | ``` 22 | 23 | ### Building/Using 24 | 25 | ```shell 26 | $ cargo build # build it after cloning 27 | ``` 28 | 29 | ````shell 30 | $ cargo run -- --ohq-ref @hrbrmstr/just-one-more-thing --output-dir ./examples --verbose # run it after cloning 31 | Finished dev [unoptimized + debuginfo] target(s) in 0.08s 32 | Running `target/debug/ohq2quarto --ohq-ref '@hrbrmstr/just-one-more-thing' --output-dir ./examples --verbose` 33 | Title: Just One More Thing 34 | Slug: just-one-more-thing 35 | Author(s): boB Rudis 36 | Copyright: Copyright 2022 boB Rudis 37 | License: "mit" 38 | Observable: https://observablehq.com/@hrbrmstr/just-one-more-thing 39 | 40 | $ tree examples 41 | ├── _quarto.yml 42 | ├── columbo_data.csv 43 | └── just-one-more-thing.qmd 44 | 45 | $ head -16 examples/just-one-more-thing.qmd 46 | --- 47 | title: 'Just One More Thing' 48 | author: 'boB Rudis' 49 | format: html 50 | echo: false 51 | observable: 'https://observablehq.com/@hrbrmstr/just-one-more-thing' 52 | --- 53 | 54 | ```{ojs} 55 | md`# Just One More Thing` 56 | ``` 57 | 58 | ```{ojs} 59 | md`This week, Chris Holmes tweeted something super dangerous:` 60 | ``` 61 | ```` 62 | 63 | ```shell 64 | $ cargo run -- --help 65 | Finished dev [unoptimized + debuginfo] target(s) in 0.08s 66 | Running `target/debug/ohq2quarto --help` 67 | ohq2quarto 0.1.0 68 | boB Rudis (@hrbrmstr) 69 | Given an Observable Notebook reference, create a Quarto project with all FileAttachments 70 | 71 | USAGE: 72 | ohq2quarto [OPTIONS] --ohq-ref --output-dir 73 | 74 | OPTIONS: 75 | --echo turn cell echo on in the Quarto document (default is to not 76 | echo) 77 | --filename optional filename for the main Quarto document (will be taken 78 | from the slug in `ohq_ref`; e.g. "just-the-facts" from the 79 | example param) 80 | -h, --help Print help information 81 | --ohq-ref an Observable notebook short reference 82 | ("@hrbrmstr/just-the-facts") or a full URL 83 | --output-dir directory to place Quarto project and files (will be created if 84 | it does not exist) 85 | -V, --version Print version information 86 | --verbose Print Notebook metadata during processing 87 | ``` 88 | -------------------------------------------------------------------------------- /examples/_quarto.yml: -------------------------------------------------------------------------------- 1 | project: 2 | title: Just One More Thing 3 | -------------------------------------------------------------------------------- /examples/columbo_data.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrbrmstr/ohq2quarto/b993271261abfe1f61bd88d827f4841fbbb9cd51/examples/columbo_data.csv -------------------------------------------------------------------------------- /examples/just-one-more-thing.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Just One More Thing' 3 | author: 'boB Rudis' 4 | format: html 5 | echo: false 6 | observable: 'https://observablehq.com/@hrbrmstr/just-one-more-thing' 7 | --- 8 | 9 | ```{ojs} 10 | md`# Just One More Thing` 11 | ``` 12 | 13 | ```{ojs} 14 | md`This week, Chris Holmes tweeted something super dangerous:` 15 | ``` 16 | 17 | ```{ojs} 18 | tweet("1560255501035880448") 19 | ``` 20 | 21 | ```{ojs} 22 | md`"Dangerous" in the sense that I really don't care much about cars or penguins, but I do 🤎 [Columbo](https://www.imdb.com/title/tt1466074/). 23 | 24 | Since more folks are likely going to be using [Quarto](https://quarto.org), and Quarto can embed Observable chunks quite nicely, I figured having a few EDA examples to riff off of might be useful. 25 | 26 | This was a quick hack today, but I'll keep adding to it now that I have a fun dataset to play with.` 27 | ``` 28 | 29 | ```{ojs} 30 | md`### Columbo Episodes Per Year` 31 | ``` 32 | 33 | ```{ojs} 34 | md`This is a basic bar chart that shows how to use group counts and a custom discrete range.` 35 | ``` 36 | 37 | ```{ojs} 38 | Plot.plot({ 39 | width: 1000, 40 | caption: html`Number of Columbo episodes per-year`, 41 | x: { 42 | label: null, 43 | domain: d3 44 | .range( 45 | d3.min(columbo, (d) => Number(d.year)), 46 | d3.max(columbo, (d) => Number(d.year)), 47 | 1 48 | ) 49 | .map((d) => d.toString()) 50 | }, 51 | y: { 52 | label: "# Episodes", 53 | grid: true 54 | }, 55 | marks: [ 56 | Plot.barY( 57 | columbo, 58 | Plot.groupX({ y: "count" }, { x: "year", fill: "#b59475" }) 59 | ), 60 | Plot.ruleY([0]) 61 | ] 62 | }) 63 | 64 | ``` 65 | 66 | ```{ojs} 67 | md`### Columbo Directors` 68 | ``` 69 | 70 | ```{ojs} 71 | md`This is similar to the above, but uses the Y axis for the discrete scale.` 72 | ``` 73 | 74 | ```{ojs} 75 | Plot.plot({ 76 | width: 1000, 77 | x: { 78 | label: null, 79 | grid: true, 80 | ticks: 8 81 | }, 82 | y: { 83 | label: null 84 | }, 85 | caption: html`Columbo Episode Directors`, 86 | marginLeft: 100, 87 | marks: [ 88 | Plot.barX( 89 | columbo, 90 | Plot.groupY( 91 | { x: "count" }, 92 | { y: "directed_by", fill: "#b59475", sort: { y: "x", reverse: true } } 93 | ) 94 | ), 95 | Plot.ruleX([0]) 96 | ] 97 | }) 98 | ``` 99 | 100 | ```{ojs} 101 | md`### When did Columbo first appear?` 102 | ``` 103 | 104 | ```{ojs} 105 | md`This one is primarily intended to show sorting.` 106 | ``` 107 | 108 | ```{ojs} 109 | Plot.plot({ 110 | width: 1000, 111 | x: { 112 | label: null, 113 | grid: true 114 | }, 115 | y: { 116 | label: "Episode #", 117 | domain: d3 118 | .sort(columbo, (d) => d.columbo_first_appearance) 119 | .map((d) => d.episode_index) 120 | }, 121 | caption: "When did Columbo first appear?", 122 | marks: [ 123 | Plot.barX(columbo, { 124 | x: "columbo_first_appearance", 125 | y: "episode_index", 126 | fill: "#b59475" 127 | }), 128 | Plot.ruleY([0]) 129 | ] 130 | }) 131 | 132 | // columbo_first_appearance 133 | ``` 134 | 135 | ```{ojs} 136 | columbo = FileAttachment("columbo_data.csv") 137 | .csv({ typed: false }) 138 | .then((data) => { 139 | return data.map((row) => { 140 | row.season = Number(row.season); 141 | row.episode = Number(row.episode); 142 | row.episode_index = Number(row.episode_index); 143 | row.columbo_first_appearance = Number(row.columbo_first_appearance); 144 | row.run_time = Number(row.run_time); 145 | row.year = row.original_air_date.substring(0, 4); 146 | return row; 147 | }); 148 | }) 149 | ``` 150 | 151 | ```{ojs} 152 | import { tweet } from "@observablehq/tweet" 153 | ``` 154 | 155 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | # This is a justfile (https://github.com/casey/just) 2 | 3 | # render project 4 | build-and-sign: 5 | cargo build --target=aarch64-apple-darwin --release && \ 6 | cargo build --target=x86_64-apple-darwin --release && \ 7 | lipo -create -output "${HOME}/bin/ohq2quarto" target/aarch64-apple-darwin/release/ohq2quarto target/x86_64-apple-darwin/release/ohq2quarto && \ 8 | codesign --force --verify --verbose --sign "${APPLE_SIGN}" "${HOME}/bin/ohq2quarto" 9 | 10 | # x86_64-pc-windows-gnu 11 | build-x86_64-pc-windows-gnu: 12 | cross build --target x86_64-pc-windows-gnu 13 | 14 | # aarch64-unknown-linux-gnu 15 | build-aarch64-unknown-linux-gnu: 16 | cross build --target aarch64-unknown-linux-gnu 17 | 18 | # i686-pc-windows-gnu 19 | build-i686-pc-windows-gnu: 20 | cross build --target i686-pc-windows-gnu 21 | 22 | # i686-pc-windows-msvc 23 | build-i686-pc-windows-msvc: 24 | cross build --target i686-pc-windows-msvc 25 | 26 | # i686-unknown-linux-gnu 27 | build-i686-unknown-linux-gnu: 28 | cross build --target i686-unknown-linux-gnu 29 | 30 | # x86_64-pc-windows-msvc 31 | build-x86_64-pc-windows-msvc: 32 | cross build --target x86_64-pc-windows-msvc 33 | 34 | # x86_64-unknown-linux-gnu 35 | build-x86_64-unknown-linux-gnu: 36 | cross build --target x86_64-unknown-linux-gnu 37 | -------------------------------------------------------------------------------- /notebook-embedded-json-cells-and-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "props": 3 | { 4 | "pageProps": 5 | { 6 | "initialNotebook": 7 | { 8 | "id": "bcaf8c209f1a283f", 9 | "slug": "tidy-js-intro-demo", 10 | "trashed": false, 11 | "description": "", 12 | "like": false, 13 | "likes": 108, 14 | "publish_level": "public", 15 | "forks": 2, 16 | "fork_of": null, 17 | "update_time": "2021-02-24T17:25:21.441Z", 18 | "publish_time": "2021-02-02T16:36:21.176Z", 19 | "publish_version": 645, 20 | "latest_version": 645, 21 | "thumbnail": "f4e0b02c026359561c55c81f4e5974b0a789a15152645a2ea5bb0313759463fe", 22 | "default_thumbnail": "f4e0b02c026359561c55c81f4e5974b0a789a15152645a2ea5bb0313759463fe", 23 | "roles": 24 | [ 25 | "user" 26 | ], 27 | "sharing": null, 28 | "subscription": null, 29 | "owner": 30 | { 31 | "id": "26ab57b8a43a5dda", 32 | "github_login": "pbeshai", 33 | "avatar_url": "https://avatars.observableusercontent.com/avatar/db43a100f98fe00872d57f02fe3deb1fe4d5305239db9310ba983a9c7d6efb65", 34 | "login": "pbeshai", 35 | "name": "Peter Beshai", 36 | "bio": "", 37 | "home_url": "http://peterbeshai.com", 38 | "type": "individual", 39 | "tier": "basic" 40 | }, 41 | "creator": 42 | { 43 | "id": "26ab57b8a43a5dda", 44 | "github_login": "pbeshai", 45 | "avatar_url": "https://avatars.observableusercontent.com/avatar/db43a100f98fe00872d57f02fe3deb1fe4d5305239db9310ba983a9c7d6efb65", 46 | "login": "pbeshai", 47 | "name": "Peter Beshai", 48 | "bio": "", 49 | "home_url": "http://peterbeshai.com", 50 | "tier": "basic" 51 | }, 52 | "authors": 53 | [ 54 | { 55 | "id": "26ab57b8a43a5dda", 56 | "avatar_url": "https://avatars.observableusercontent.com/avatar/db43a100f98fe00872d57f02fe3deb1fe4d5305239db9310ba983a9c7d6efb65", 57 | "name": "Peter Beshai", 58 | "login": "pbeshai", 59 | "bio": "", 60 | "home_url": "http://peterbeshai.com", 61 | "github_login": "pbeshai", 62 | "tier": "basic", 63 | "approved": true, 64 | "description": "" 65 | } 66 | ], 67 | "collections": 68 | [ 69 | { 70 | "id": "5610fa9dc3f1eab0", 71 | "type": "public", 72 | "slug": "tidy-js", 73 | "title": "tidy.js", 74 | "description": "Examples of using tidy.js to wrangle data", 75 | "update_time": "2021-02-24T22:18:57.641Z", 76 | "pinned": false, 77 | "ordered": false, 78 | "custom_thumbnail": null, 79 | "default_thumbnail": "42469726dbbe2a563c5e6e8e7e7dc36399a5d06ee2d97eec85c7c2b85fcef306", 80 | "thumbnail": "42469726dbbe2a563c5e6e8e7e7dc36399a5d06ee2d97eec85c7c2b85fcef306", 81 | "listing_count": 6, 82 | "parent_collection_count": 0, 83 | "owner": 84 | { 85 | "id": "26ab57b8a43a5dda", 86 | "github_login": "pbeshai", 87 | "avatar_url": "https://avatars.observableusercontent.com/avatar/db43a100f98fe00872d57f02fe3deb1fe4d5305239db9310ba983a9c7d6efb65", 88 | "login": "pbeshai", 89 | "name": "Peter Beshai", 90 | "bio": "", 91 | "home_url": "http://peterbeshai.com", 92 | "type": "individual", 93 | "tier": "basic" 94 | } 95 | } 96 | ], 97 | "files": 98 | [ 99 | { 100 | "id": "1a831ee725a0053f20a838aa739750ee7bf8b7f662d29117a01915902323462e4fe30f0d960ddb4728bc25aa4b3b902c4cdfe49de427b2505bc6cebe5c3d3b5a", 101 | "url": "https://static.observableusercontent.com/files/1a831ee725a0053f20a838aa739750ee7bf8b7f662d29117a01915902323462e4fe30f0d960ddb4728bc25aa4b3b902c4cdfe49de427b2505bc6cebe5c3d3b5a", 102 | "download_url": "https://static.observableusercontent.com/files/1a831ee725a0053f20a838aa739750ee7bf8b7f662d29117a01915902323462e4fe30f0d960ddb4728bc25aa4b3b902c4cdfe49de427b2505bc6cebe5c3d3b5a?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27who.csv", 103 | "name": "who.csv", 104 | "create_time": "2021-02-01T19:28:34.759Z", 105 | "status": "public", 106 | "size": 1391363, 107 | "mime_type": "text/csv", 108 | "content_encoding": "gzip" 109 | }, 110 | { 111 | "id": "afa8a222c392586a3cc8013f91732625afcc1f9994bf9660daccef35340ddf1957610a8ad7170b911074c1d801720f365e2407c3616b7f1d6ec47f6c61ece8f0", 112 | "url": "https://static.observableusercontent.com/files/afa8a222c392586a3cc8013f91732625afcc1f9994bf9660daccef35340ddf1957610a8ad7170b911074c1d801720f365e2407c3616b7f1d6ec47f6c61ece8f0", 113 | "download_url": "https://static.observableusercontent.com/files/afa8a222c392586a3cc8013f91732625afcc1f9994bf9660daccef35340ddf1957610a8ad7170b911074c1d801720f365e2407c3616b7f1d6ec47f6c61ece8f0?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27tidy.js", 114 | "name": "tidy.js", 115 | "create_time": "2021-02-02T15:53:40.367Z", 116 | "status": "public", 117 | "size": 40889, 118 | "mime_type": "application/javascript", 119 | "content_encoding": "gzip" 120 | } 121 | ], 122 | "comments": 123 | [ 124 | { 125 | "id": "166e9873aa4ff4eb", 126 | "content": "The Markdown says the imports are from '@tidyjs/tidyjs when it's actually from '@pbeshai/tidyjs :D", 127 | "node_id": 425, 128 | "create_time": "2022-03-08T20:53:06.414Z", 129 | "update_time": null, 130 | "resolved": false, 131 | "user": 132 | { 133 | "id": "71df97e2192b6fce", 134 | "github_login": "hellonearthis", 135 | "avatar_url": "https://avatars.observableusercontent.com/avatar/ee05023c4c2a9d53e29a4f85f35e35430ae16d2c4aa6a6ca791175caf667abc2", 136 | "login": "hellonearthis", 137 | "name": "Brett Cooper", 138 | "bio": "In New Zealand being kid of human.\n\nWe might make mistakes but we will make other things too. - \nMichael Joseph Savage", 139 | "home_url": "https://twitter.com/hellonearthis", 140 | "tier": "basic" 141 | } 142 | } 143 | ], 144 | "commenting_lock": null, 145 | "suggestion_from": null, 146 | "suggestions_to": 147 | [], 148 | "version": 645, 149 | "title": "tidy.js – Intro & Demo", 150 | "license": null, 151 | "copyright": "", 152 | "nodes": 153 | [ 154 | { 155 | "id": 0, 156 | "value": "md`# tidy.js – Intro & Demo\n\n**Tidy up your data with JavaScript!** Inspired by [dplyr](https://dplyr.tidyverse.org/) and the [tidyverse](https://www.tidyverse.org/), tidy.js attempts to bring the ergonomics of data manipulation from R to javascript (and typescript). The primary goals of the project are:\n\n* **Readable code**. Tidy.js prioritizes making your data transformations readable, so future you and your teammates can get up and running quickly.\n\n* **Standard transformation verbs**. Tidy.js is built using battle-tested verbs from the R community that can handle any data wrangling need.\n\n* **Work with plain JS objects**. No wrapper classes needed — all tidy.js needs is an array of plain old-fashioned JS objects to get started. Simple in, simple out.\n\nSecondarily, this project aims to provide acceptable types for the functions provided.\n\n#### Shout out to Netflix\n\nI want to give a big shout out to [Netflix](https://research.netflix.com/), my current employer, for giving me the opportunity to work on this project and to open source it. It's a great place to work and if you enjoy tinkering with data-related things, I'd strongly recommend checking out [our analytics department](https://research.netflix.com/research-area/analytics).\n\n#### Quick Links\n\n* [GitHub repo](https://github.com/pbeshai/tidy)\n* [Project homepage](https://pbeshai.github.io/tidy)\n* [API reference documentation](https://pbeshai.github.io/tidy/docs/api/tidy)\n* [Playground](https://pbeshai.github.io/tidy/playground)\n\n#### Related work\n\nBe sure to check out a very similar project, [Arquero](https://github.com/uwdata/arquero), from [UW Data](https://idl.cs.washington.edu/). \n\n\n## Getting started\n\nTo start using tidy, your best bet is to install from npm:\n\n\\`\\`\\`shell\nnpm install @tidyjs/tidy\n# or\nyarn add @tidyjs/tidy\n\\`\\`\\`\n\nThen import the functions you need:\n\n\\`\\`\\`js\nimport { tidy, mutate, arrange, desc } from '@tidyjs/tidy'\n\\`\\`\\`\n\nAnd use them on an array of objects:\n\n\\`\\`\\`js\nconst data = [\n { a: 1, b: 10 }, \n { a: 3, b: 12 }, \n { a: 2, b: 10 }\n]\n\nconst results = tidy(\n data, \n mutate({ ab: d => d.a * d.b }),\n arrange(desc('ab'))\n)\n\\`\\`\\`\n\nThe output is:\n\n\\`\\`\\`js\n[\n { a: 3, b: 12, ab: 36},\n { a: 2, b: 10, ab: 20},\n { a: 1, b: 10, ab: 10}\n]\n\\`\\`\\`\n\n`", 157 | "pinned": false, 158 | "mode": "js", 159 | "data": null, 160 | "name": null 161 | }, 162 | { 163 | "id": 423, 164 | "value": "{\n const data = [{ a: 1, b: 10 }, { a: 3, b: 12 }, { a: 2, b: 10 }];\n\n return tidy(data, mutate({ ab: d => d.a * d.b }), arrange(desc('ab')));\n}", 165 | "pinned": false, 166 | "mode": "js", 167 | "data": null, 168 | "name": null 169 | }, 170 | { 171 | "id": 425, 172 | "value": "md`\nAll tidy.js code is wrapped in a **tidy flow** via the \\`tidy()\\` function. The first argument is the array of data, followed by the transformation verbs to run on the data. The actual functions passed to \\`tidy()\\` can be anything so long as they fit the form:\n\n\\`\\`\\`\n(items: object[]) => object[]\n\\`\\`\\`\n\nFor example, the following is valid:\n\n\\`\\`\\`js\ntidy(\n data, \n items => items.filter((d, i) => i % 2 === 0),\n arrange(desc('value'))\n)\n\\`\\`\\`\n\nAll tidy verbs fit this style, with the exception of exports from groupBy, discussed below.\n\n### Grouping data with groupBy\n\nBesides manipulating flat lists of data, tidy provides facilities for wrangling grouped data via the \\`groupBy()\\` function.\n\n\\`\\`\\`js\nimport { tidy, summarize, sum, groupBy } from '@tidyjs/tidy'\n\nconst data = [\n { key: 'group1', value: 10 }, \n { key: 'group2', value: 9 }, \n { key: 'group1', value: 7 }\n]\n\ntidy(\n data,\n groupBy('key', [\n summarize({ total: sum('value') })\n ])\n)\n\n\\`\\`\\`\n\nThe output is:\n\\`\\`\\`js\n[\n { \"key\": \"group1\", \"total\": 17 },\n { \"key\": \"group2\", \"total\": 9 },\n]\n\\`\\`\\`\n`", 173 | "pinned": false, 174 | "mode": "js", 175 | "data": null, 176 | "name": null 177 | }, 178 | { 179 | "id": 434, 180 | "value": "{\n const data = [\n { key: 'group1', value: 10 },\n { key: 'group2', value: 9 },\n { key: 'group1', value: 7 }\n ];\n\n return tidy(data, groupBy('key', [summarize({ total: sum('value') })]));\n}", 181 | "pinned": false, 182 | "mode": "js", 183 | "data": null, 184 | "name": null 185 | }, 186 | { 187 | "id": 431, 188 | "value": "md`\nThe \\`groupBy()\\` function works similarly to \\`tidy()\\` in that it takes a flow of functions as its second argument (wrapped in an array). Things get really fun when you use groupBy's *third* argument for exporting the grouped data into different shapes. \n\nFor example, exporting data as a nested object, we can use \\`groupBy.object()\\` as the third argument to \\`groupBy()\\`.\n \n\\`\\`\\`js\nconst data = [\n { g: 'a', h: 'x', value: 5 },\n { g: 'a', h: 'y', value: 15 },\n { g: 'b', h: 'x', value: 10 },\n { g: 'b', h: 'x', value: 20 },\n { g: 'b', h: 'y', value: 30 },\n]\n\ntidy(\n data,\n groupBy(\n ['g', 'h'], \n [\n mutate({ key: d => \\`\\${d.g}\\${d.h}\\`})\n ], \n groupBy.object() // <-- specify the export\n )\n);\n\n\\`\\`\\`\n\nThe output is:\n\n\\`\\`\\`js\n{\n \"a\": {\n \"x\": [{\"g\": \"a\", \"h\": \"x\", \"value\": 5, \"key\": \"ax\"}],\n \"y\": [{\"g\": \"a\", \"h\": \"y\", \"value\": 15, \"key\": \"ay\"}]\n },\n \"b\": {\n \"x\": [\n {\"g\": \"b\", \"h\": \"x\", \"value\": 10, \"key\": \"bx\"},\n {\"g\": \"b\", \"h\": \"x\", \"value\": 20, \"key\": \"bx\"}\n ],\n \"y\": [{\"g\": \"b\", \"h\": \"y\", \"value\": 30, \"key\": \"by\"}]\n }\n}\n\\`\\`\\`\n\n`", 189 | "pinned": false, 190 | "mode": "js", 191 | "data": null, 192 | "name": null 193 | }, 194 | { 195 | "id": 447, 196 | "value": "{\n const data = [\n { g: 'a', h: 'x', value: 5 },\n { g: 'a', h: 'y', value: 15 },\n { g: 'b', h: 'x', value: 10 },\n { g: 'b', h: 'x', value: 20 },\n { g: 'b', h: 'y', value: 30 }\n ];\n\n return tidy(\n data,\n groupBy(\n ['g', 'h'],\n [mutate({ key: d => `${d.g}${d.h}` })],\n groupBy.object() // <-- specify the export\n )\n );\n}", 197 | "pinned": false, 198 | "mode": "js", 199 | "data": null, 200 | "name": null 201 | }, 202 | { 203 | "id": 444, 204 | "value": "md`\n\nOr alternatively as \\`{ key, values }\\` entries-objects via \\`groupBy.entriesObject()\\`:\n\n\\`\\`\\`js\ntidy(data,\n groupBy(\n ['g', 'h'], \n [\n mutate({ key: d => \\`\\${d.g}\\${d.h}\\`})\n ], \n groupBy.entriesObject() // <-- specify the export\n )\n);\n\\`\\`\\`\n\nThe output is:\n\n\\`\\`\\`js\n[\n {\n \"key\": \"a\",\n \"values\": [\n {\"key\": \"x\", \"values\": [{\"g\": \"a\", \"h\": \"x\", \"value\": 5, \"key\": \"ax\"}]},\n {\"key\": \"y\", \"values\": [{\"g\": \"a\", \"h\": \"y\", \"value\": 15, \"key\": \"ay\"}]}\n ]\n },\n {\n \"key\": \"b\",\n \"values\": [\n {\n \"key\": \"x\",\n \"values\": [\n {\"g\": \"b\", \"h\": \"x\", \"value\": 10, \"key\": \"bx\"},\n {\"g\": \"b\", \"h\": \"x\", \"value\": 20, \"key\": \"bx\"}\n ]\n },\n {\"key\": \"y\", \"values\": [{\"g\": \"b\", \"h\": \"y\", \"value\": 30, \"key\": \"by\"}]}\n ]\n }\n]\n\\`\\`\\`\n`", 205 | "pinned": false, 206 | "mode": "js", 207 | "data": null, 208 | "name": null 209 | }, 210 | { 211 | "id": 471, 212 | "value": "{\n const data = [\n { g: 'a', h: 'x', value: 5 },\n { g: 'a', h: 'y', value: 15 },\n { g: 'b', h: 'x', value: 10 },\n { g: 'b', h: 'x', value: 20 },\n { g: 'b', h: 'y', value: 30 }\n ];\n return tidy(\n data,\n groupBy(\n ['g', 'h'],\n [mutate({ key: d => `${d.g}${d.h}` })],\n groupBy.entriesObject() // <-- specify the export\n )\n );\n}", 213 | "pinned": false, 214 | "mode": "js", 215 | "data": null, 216 | "name": null 217 | }, 218 | { 219 | "id": 487, 220 | "value": "md`\nIt's common to be left with a single leaf in a groupBy set, especially after running summarize(). To prevent your exported data having its values wrapped in an array, you can pass the \\`single\\` option to it.\n\n\\`\\`\\`js\ntidy(input,\n groupBy(['g', 'h'], [\n summarize({ total: sum('value') })\n ], groupBy.object({ single: true }))\n);\n\\`\\`\\`\n\nThe output is:\n\n\\`\\`\\`js\n{\n \"a\": {\n \"x\": {\"total\": 5, \"g\": \"a\", \"h\": \"x\"},\n \"y\": {\"total\": 15, \"g\": \"a\", \"h\": \"y\"}\n },\n \"b\": {\n \"x\": {\"total\": 30, \"g\": \"b\", \"h\": \"x\"},\n \"y\": {\"total\": 30, \"g\": \"b\", \"h\": \"y\"}\n }\n}\n\\`\\`\\`\n\n`\n", 221 | "pinned": false, 222 | "mode": "js", 223 | "data": null, 224 | "name": null 225 | }, 226 | { 227 | "id": 488, 228 | "value": "{\n const data = [\n { g: 'a', h: 'x', value: 5 },\n { g: 'a', h: 'y', value: 15 },\n { g: 'b', h: 'x', value: 10 },\n { g: 'b', h: 'x', value: 20 },\n { g: 'b', h: 'y', value: 30 }\n ];\n return tidy(\n data,\n groupBy(\n ['g', 'h'],\n [summarize({ total: sum('value') })],\n groupBy.object({ single: true })\n )\n );\n}", 229 | "pinned": false, 230 | "mode": "js", 231 | "data": null, 232 | "name": null 233 | }, 234 | { 235 | "id": 467, 236 | "value": "md`\nVisit the [API reference docs](https://pbeshai.github.io/tidy/docs/api/tidy) to learn more about how each function works and all the options they take. Be sure to check out the \\`levels\\` export, which can let you mix-and-match different export types based on the depth of the data. For quick reference, other available groupBy exports include: \n\n* groupBy.entries()\n* groupBy.entriesObject()\n* groupBy.grouped()\n* groupBy.levels()\n* groupBy.object()\n* groupBy.keys()\n* groupBy.map()\n* groupBy.values()\n\nThe rest of this notebook will work through [Hadley Wickham's Tidy Data](https://r4ds.had.co.nz/tidy-data.html) chapter, replicated in tidy.js, but first a playground.\n\n`", 237 | "pinned": false, 238 | "mode": "js", 239 | "data": null, 240 | "name": null 241 | }, 242 | { 243 | "id": 57, 244 | "value": "md`\n---\n\n# Playground\nHere's a playground for you to give tidy.js a shot. Edit the code below and see a table showing the output. Many tidy functions have been added directly to the workbook, but the rest are available under \\`T.*\\` e.g. \\`T.mutateWithSummary({ ... })\\` \n`", 245 | "pinned": false, 246 | "mode": "js", 247 | "data": null, 248 | "name": null 249 | }, 250 | { 251 | "id": 18, 252 | "value": "output = tidy(\n cars,\n groupBy(\n ['cyl', 'gear'],\n [\n summarize({\n n: n(),\n mpg: mean('mpg'),\n hp: mean('hp'),\n wt: mean('wt')\n })\n ]\n ),\n select(['cyl', 'gear', everything()]),\n arrange([desc('n'), desc('mpg')])\n)", 253 | "pinned": true, 254 | "mode": "js", 255 | "data": null, 256 | "name": null 257 | }, 258 | { 259 | "id": 54, 260 | "value": "table(output)", 261 | "pinned": false, 262 | "mode": "js", 263 | "data": null, 264 | "name": null 265 | }, 266 | { 267 | "id": 504, 268 | "value": "md`---`", 269 | "pinned": false, 270 | "mode": "js", 271 | "data": null, 272 | "name": null 273 | }, 274 | { 275 | "id": 110, 276 | "value": "md`# Replicating Hadley Wickham's Tidy Data chapter\n\n Similar to [Arquero's notebook](https://observablehq.com/@jheer/tidy-data-in-javascript), this notebook will also try to replicate [Hadley Wickham's Tidy Data](https://r4ds.had.co.nz/tidy-data.html) to demonstrate how to use tidy.js. \n\n## 12.2 Tidy data\n\nComputing rates per 10,000 with **mutate()**, **rate()**, and **TMath.rate()**\n`", 277 | "pinned": false, 278 | "mode": "js", 279 | "data": null, 280 | "name": null 281 | }, 282 | { 283 | "id": 115, 284 | "value": "table(\n tidy(\n table1,\n mutate({\n rate: d => (d.cases / d.population) * 10000,\n\n '|': '|', // visual spacer for table()\n // alternatively, use the `rate()` function for safety around division by 0 and nully values\n // rate2: rate('cases', 'population'),\n rate2: rate(d => d.cases * 10000, 'population'),\n // or the TMath.rate function directly:\n rate3: d => TMath.rate(d.cases, d.population) * 10000\n })\n )\n)", 285 | "pinned": true, 286 | "mode": "js", 287 | "data": null, 288 | "name": null 289 | }, 290 | { 291 | "id": 515, 292 | "value": "md`Compute cases per year with a weighted **count()**`", 293 | "pinned": false, 294 | "mode": "js", 295 | "data": null, 296 | "name": null 297 | }, 298 | { 299 | "id": 156, 300 | "value": "table(tidy(table1, count('year', { wt: 'cases' })))", 301 | "pinned": true, 302 | "mode": "js", 303 | "data": null, 304 | "name": null 305 | }, 306 | { 307 | "id": 176, 308 | "value": "md`## 12.3 Pivoting\n\nKeywords include: gather, spread, melt, reshape2, pivot_longer, pivot_wider\n\n### 12.3.1 Pivot Longer\n\nInitial table\n`", 309 | "pinned": false, 310 | "mode": "js", 311 | "data": null, 312 | "name": null 313 | }, 314 | { 315 | "id": 187, 316 | "value": "table(table4a)", 317 | "pinned": true, 318 | "mode": "js", 319 | "data": null, 320 | "name": null 321 | }, 322 | { 323 | "id": 521, 324 | "value": "md`Pivot longer based on the years columns`", 325 | "pinned": false, 326 | "mode": "js", 327 | "data": null, 328 | "name": null 329 | }, 330 | { 331 | "id": 178, 332 | "value": "table(\n tidy(\n table4a,\n pivotLonger({\n cols: ['1999', '2000'],\n namesTo: 'year',\n valuesTo: 'cases'\n })\n )\n)", 333 | "pinned": true, 334 | "mode": "js", 335 | "data": null, 336 | "name": null 337 | }, 338 | { 339 | "id": 524, 340 | "value": "md`Do the same thing on a second table with population data`", 341 | "pinned": false, 342 | "mode": "js", 343 | "data": null, 344 | "name": null 345 | }, 346 | { 347 | "id": 181, 348 | "value": "table(\n tidy(\n table4b,\n pivotLonger({\n cols: ['1999', '2000'],\n namesTo: 'year',\n valuesTo: 'population'\n })\n )\n)", 349 | "pinned": true, 350 | "mode": "js", 351 | "data": null, 352 | "name": null 353 | }, 354 | { 355 | "id": 527, 356 | "value": "md`Now join them together with the ever-handy **leftJoin()**`", 357 | "pinned": false, 358 | "mode": "js", 359 | "data": null, 360 | "name": null 361 | }, 362 | { 363 | "id": 197, 364 | "value": "{\n let tidy4a = tidy(\n table4a,\n pivotLonger({\n cols: ['1999', '2000'],\n namesTo: 'year',\n valuesTo: 'cases'\n })\n );\n let tidy4b = tidy(\n table4b,\n pivotLonger({\n cols: ['1999', '2000'],\n namesTo: 'year',\n valuesTo: 'population'\n })\n );\n\n return table(tidy(tidy4a, leftJoin(tidy4b)));\n}", 365 | "pinned": true, 366 | "mode": "js", 367 | "data": null, 368 | "name": null 369 | }, 370 | { 371 | "id": 206, 372 | "value": "md`### 12.3.2 Pivot Wider\n\nThe opposite of pivotLonger, we've got **pivotWider()**. Initial table:\n`", 373 | "pinned": false, 374 | "mode": "js", 375 | "data": null, 376 | "name": null 377 | }, 378 | { 379 | "id": 208, 380 | "value": "table(table2)", 381 | "pinned": true, 382 | "mode": "js", 383 | "data": null, 384 | "name": null 385 | }, 386 | { 387 | "id": 531, 388 | "value": "md`Pivot wider, making cases and population new columns and dropping type and count.`", 389 | "pinned": false, 390 | "mode": "js", 391 | "data": null, 392 | "name": null 393 | }, 394 | { 395 | "id": 211, 396 | "value": "table(tidy(table2, pivotWider({ namesFrom: 'type', valuesFrom: 'count' })))", 397 | "pinned": true, 398 | "mode": "js", 399 | "data": null, 400 | "name": null 401 | }, 402 | { 403 | "id": 533, 404 | "value": "md`Optionally, you can re-arrange keys in the output with **select()**:`", 405 | "pinned": false, 406 | "mode": "js", 407 | "data": null, 408 | "name": null 409 | }, 410 | { 411 | "id": 217, 412 | "value": "// or re-arrange\ntable(\n tidy(\n table2,\n pivotWider({ namesFrom: 'type', valuesFrom: 'count' }),\n select(['country', 'year', everything()])\n )\n)", 413 | "pinned": true, 414 | "mode": "js", 415 | "data": null, 416 | "name": null 417 | }, 418 | { 419 | "id": 225, 420 | "value": "md`## 12.4 Separating and uniting\n\nTidy.js doesn't currently provide **separate()** or **unite()**, but they are similarly accomplished with **mutate()** and **select()**. If there's enough need, they may be added.\n\n### 12.4.1 Separate\n\nInitial data`", 421 | "pinned": false, 422 | "mode": "js", 423 | "data": null, 424 | "name": null 425 | }, 426 | { 427 | "id": 229, 428 | "value": "table(table3)", 429 | "pinned": true, 430 | "mode": "js", 431 | "data": null, 432 | "name": null 433 | }, 434 | { 435 | "id": 540, 436 | "value": "md`Split the rate column into two columns: case and population, similar to separate():`", 437 | "pinned": false, 438 | "mode": "js", 439 | "data": null, 440 | "name": null 441 | }, 442 | { 443 | "id": 231, 444 | "value": "// tidy doesn't provide a separate command (yet?), but you could use mutate\ntable(\n tidy(\n table3,\n mutate({\n rate: d => d.rate.split('/'),\n cases: d => +d.rate[0],\n population: d => +d.rate[1]\n }),\n select('-rate')\n )\n)", 445 | "pinned": true, 446 | "mode": "js", 447 | "data": null, 448 | "name": null 449 | }, 450 | { 451 | "id": 549, 452 | "value": "md`Alternatively, you can always use map() instead of mutate():`", 453 | "pinned": false, 454 | "mode": "js", 455 | "data": null, 456 | "name": null 457 | }, 458 | { 459 | "id": 234, 460 | "value": "// or you could use map\ntable(\n tidy(\n table3,\n map(d => {\n const [cases, population] = d.rate.split('/').map(x => +x);\n return { ...d, cases, population };\n }),\n select('-rate')\n )\n)", 461 | "pinned": true, 462 | "mode": "js", 463 | "data": null, 464 | "name": null 465 | }, 466 | { 467 | "id": 244, 468 | "value": "md`### 12.4.2 Unite\n\ntidy.js doesn't currently provide unite, but we can use **mutate()** and **select()** to concatenate century and year:`", 469 | "pinned": false, 470 | "mode": "js", 471 | "data": null, 472 | "name": null 473 | }, 474 | { 475 | "id": 246, 476 | "value": "// there's no unite (yet), but a mutate + select will do the trick\ntable(\n tidy(\n table5,\n mutate({ new: d => `${d.century}_${d.year}` }),\n select(['-century', '-year'])\n )\n)", 477 | "pinned": true, 478 | "mode": "js", 479 | "data": null, 480 | "name": null 481 | }, 482 | { 483 | "id": 560, 484 | "value": "md`We can combine however we please with whatever js code we'd like, including converting from string to number by prefixing with \\`+\\``", 485 | "pinned": false, 486 | "mode": "js", 487 | "data": null, 488 | "name": null 489 | }, 490 | { 491 | "id": 266, 492 | "value": "table(\n tidy(\n table5,\n mutate({ new: d => +`${d.century}${d.year}` }),\n select(['-century', '-year'])\n )\n)", 493 | "pinned": true, 494 | "mode": "js", 495 | "data": null, 496 | "name": null 497 | }, 498 | { 499 | "id": 277, 500 | "value": "md`## 12.5 Missing Values\n\nInitial data:`", 501 | "pinned": false, 502 | "mode": "js", 503 | "data": null, 504 | "name": null 505 | }, 506 | { 507 | "id": 283, 508 | "value": "table(stocks)", 509 | "pinned": true, 510 | "mode": "js", 511 | "data": null, 512 | "name": null 513 | }, 514 | { 515 | "id": 564, 516 | "value": "md`By pivoting wider we can demonstrate there are missing values more clearly`", 517 | "pinned": false, 518 | "mode": "js", 519 | "data": null, 520 | "name": null 521 | }, 522 | { 523 | "id": 285, 524 | "value": "table(tidy(stocks, pivotWider({ namesFrom: 'year', valuesFrom: 'return' })))", 525 | "pinned": true, 526 | "mode": "js", 527 | "data": null, 528 | "name": null 529 | }, 530 | { 531 | "id": 566, 532 | "value": "md`We can remove rows that are missing with **filter()**, or similarly with an anonymous function like \\`items => items.filter(...)\\``", 533 | "pinned": false, 534 | "mode": "js", 535 | "data": null, 536 | "name": null 537 | }, 538 | { 539 | "id": 287, 540 | "value": "// drop out missing values\ntable(\n tidy(\n stocks,\n pivotWider({ namesFrom: 'year', valuesFrom: 'return' }),\n pivotLonger({\n cols: ['2015', '2016'],\n namesTo: 'year',\n valuesTo: 'return'\n }),\n // pivotLonger does not have values_drop_na at the moment\n filter(d => d.return != null)\n )\n)", 541 | "pinned": true, 542 | "mode": "js", 543 | "data": null, 544 | "name": null 545 | }, 546 | { 547 | "id": 568, 548 | "value": "md`We can use **complete()** to add in items that are missing from an expected sequence.`", 549 | "pinned": false, 550 | "mode": "js", 551 | "data": null, 552 | "name": null 553 | }, 554 | { 555 | "id": 296, 556 | "value": "// use complete to make missing values explicit\ntable(tidy(stocks, complete(['year', 'qtr'])))", 557 | "pinned": true, 558 | "mode": "js", 559 | "data": null, 560 | "name": null 561 | }, 562 | { 563 | "id": 570, 564 | "value": "md`Initial data for **fill()** example:`", 565 | "pinned": false, 566 | "mode": "js", 567 | "data": null, 568 | "name": null 569 | }, 570 | { 571 | "id": 300, 572 | "value": "treatment = [\n { person: 'Derrick Whitmore', treatment: 1, response: 7 },\n { treatment: 2, response: 10 },\n { person: undefined, treatment: 3, response: 9 },\n { person: 'Katherine Burke', treatment: 1, response: 4 }\n]", 573 | "pinned": true, 574 | "mode": "js", 575 | "data": null, 576 | "name": null 577 | }, 578 | { 579 | "id": 306, 580 | "value": "table(treatment)", 581 | "pinned": true, 582 | "mode": "js", 583 | "data": null, 584 | "name": null 585 | }, 586 | { 587 | "id": 573, 588 | "value": "md`We can use **fill()** to fill in missing values with their last seen value`", 589 | "pinned": false, 590 | "mode": "js", 591 | "data": null, 592 | "name": null 593 | }, 594 | { 595 | "id": 303, 596 | "value": "// fill in columns with last seen values\ntable(tidy(treatment, fill('person')))", 597 | "pinned": true, 598 | "mode": "js", 599 | "data": null, 600 | "name": null 601 | }, 602 | { 603 | "id": 308, 604 | "value": "md`## 12.6 Case Study\n\nFollow along in [Hadley's chapter](https://r4ds.had.co.nz/tidy-data.html#case-study) for context and details\n`", 605 | "pinned": false, 606 | "mode": "js", 607 | "data": null, 608 | "name": null 609 | }, 610 | { 611 | "id": 310, 612 | "value": "table(who.slice(0, 20))", 613 | "pinned": true, 614 | "mode": "js", 615 | "data": null, 616 | "name": null 617 | }, 618 | { 619 | "id": 313, 620 | "value": "who1 = tidy(\n who,\n pivotLonger({ cols: startsWith('new'), namesTo: 'key', valuesTo: 'cases' }),\n filter(d => d.cases !== 'NA')\n)", 621 | "pinned": true, 622 | "mode": "js", 623 | "data": null, 624 | "name": null 625 | }, 626 | { 627 | "id": 318, 628 | "value": "table(who1.slice(0, 20))", 629 | "pinned": true, 630 | "mode": "js", 631 | "data": null, 632 | "name": null 633 | }, 634 | { 635 | "id": 325, 636 | "value": "table(tidy(who1, count('key')))", 637 | "pinned": true, 638 | "mode": "js", 639 | "data": null, 640 | "name": null 641 | }, 642 | { 643 | "id": 332, 644 | "value": "who2 = tidy(who1, mutate({ key: d => d.key.replace('newrel', 'new_rel') }))", 645 | "pinned": true, 646 | "mode": "js", 647 | "data": null, 648 | "name": null 649 | }, 650 | { 651 | "id": 337, 652 | "value": "table(who2.filter(d => d.key.includes('new_rel')).slice(0, 15))", 653 | "pinned": true, 654 | "mode": "js", 655 | "data": null, 656 | "name": null 657 | }, 658 | { 659 | "id": 346, 660 | "value": "who3 = tidy(\n who2,\n mutate({\n key: d => d.key.split('_'),\n new: d => d.key[0],\n type: d => d.key[1],\n sexage: d => d.key[2]\n }),\n select(['-key'])\n)", 661 | "pinned": true, 662 | "mode": "js", 663 | "data": null, 664 | "name": null 665 | }, 666 | { 667 | "id": 347, 668 | "value": "table(who3.slice(0, 6))", 669 | "pinned": true, 670 | "mode": "js", 671 | "data": null, 672 | "name": null 673 | }, 674 | { 675 | "id": 356, 676 | "value": "table(tidy(who3, count('new')))", 677 | "pinned": true, 678 | "mode": "js", 679 | "data": null, 680 | "name": null 681 | }, 682 | { 683 | "id": 358, 684 | "value": "who4 = tidy(who3, select(['-new', '-iso2', '-iso3']))", 685 | "pinned": true, 686 | "mode": "js", 687 | "data": null, 688 | "name": null 689 | }, 690 | { 691 | "id": 360, 692 | "value": "who5 = tidy(\n who4,\n mutate({\n sexage: d => [d.sexage[0], d.sexage.substring(1)],\n sex: d => d.sexage[0],\n age: d => +d.sexage[1]\n }),\n select('-sexage')\n)", 693 | "pinned": true, 694 | "mode": "js", 695 | "data": null, 696 | "name": null 697 | }, 698 | { 699 | "id": 366, 700 | "value": "table(who5.slice(0, 10))", 701 | "pinned": true, 702 | "mode": "js", 703 | "data": null, 704 | "name": null 705 | }, 706 | { 707 | "id": 369, 708 | "value": "// all together now\ntable(\n tidy(\n who,\n pivotLonger({ cols: startsWith('new'), namesTo: 'key', valuesTo: 'cases' }),\n filter(d => d.cases !== 'NA'),\n mutate({ key: d => d.key.replace('newrel', 'new_rel') }),\n // separate equivalent:\n mutate({\n key: d => d.key.split('_'),\n new: d => d.key[0],\n type: d => d.key[1],\n sexage: d => d.key[2]\n }),\n // separate equivalent:\n mutate({\n sexage: d => [d.sexage[0], d.sexage.substring(1)],\n sex: d => d.sexage[0],\n age: d => +d.sexage[1]\n }),\n select(['-key', '-new', '-iso2', '-iso3', '-sexage'])\n ).slice(0, 20)\n)", 709 | "pinned": true, 710 | "mode": "js", 711 | "data": null, 712 | "name": null 713 | }, 714 | { 715 | "id": 2, 716 | "value": "md`---\n## Appendix`", 717 | "pinned": false, 718 | "mode": "js", 719 | "data": null, 720 | "name": null 721 | }, 722 | { 723 | "id": 10, 724 | "value": "viewof tidyjsfile = html``", 725 | "pinned": true, 726 | "mode": "js", 727 | "data": null, 728 | "name": null 729 | }, 730 | { 731 | "id": 14, 732 | "value": "d3 = require('d3-array', 'd3-dsv')", 733 | "pinned": true, 734 | "mode": "js", 735 | "data": null, 736 | "name": null 737 | }, 738 | { 739 | "id": 12, 740 | "value": "module = require(URL.createObjectURL(tidyjsfile))", 741 | "pinned": true, 742 | "mode": "js", 743 | "data": null, 744 | "name": null 745 | }, 746 | { 747 | "id": 119, 748 | "value": "import {\n tidy,\n startsWith,\n groupBy,\n summarize,\n sum,\n mutate,\n select,\n n,\n mean,\n everything,\n arrange,\n desc,\n filter,\n rate,\n TMath,\n pivotWider,\n pivotLonger,\n leftJoin,\n count,\n map,\n complete,\n fill\n} from '@pbeshai/tidyjs' // observable import", 749 | "pinned": true, 750 | "mode": "js", 751 | "data": null, 752 | "name": null 753 | }, 754 | { 755 | "id": 599, 756 | "value": "module_attachment = require(await FileAttachment(\"tidy.js\").url())", 757 | "pinned": true, 758 | "mode": "js", 759 | "data": null, 760 | "name": null 761 | }, 762 | { 763 | "id": 21, 764 | "value": "cars=[\n { \"name\": \"Mazda RX4\", \"mpg\": 21, \"cyl\": 6, \"disp\": 160, \"hp\": 110, \"drat\": 3.9, \"wt\": 2.62, \"qsec\": 16.46, \"vs\": 0, \"am\": 1, \"gear\": 4, \"carb\": 4 },\n { \"name\": \"Mazda RX4 Wag\", \"mpg\": 21, \"cyl\": 6, \"disp\": 160, \"hp\": 110, \"drat\": 3.9, \"wt\": 2.875, \"qsec\": 17.02, \"vs\": 0, \"am\": 1, \"gear\": 4, \"carb\": 4 },\n { \"name\": \"Datsun 710\", \"mpg\": 22.8, \"cyl\": 4, \"disp\": 108, \"hp\": 93, \"drat\": 3.85, \"wt\": 2.32, \"qsec\": 18.61, \"vs\": 1, \"am\": 1, \"gear\": 4, \"carb\": 1 },\n { \"name\": \"Hornet 4 Drive\", \"mpg\": 21.4, \"cyl\": 6, \"disp\": 258, \"hp\": 110, \"drat\": 3.08, \"wt\": 3.215, \"qsec\": 19.44, \"vs\": 1, \"am\": 0, \"gear\": 3, \"carb\": 1 },\n { \"name\": \"Hornet Sportabout\", \"mpg\": 18.7, \"cyl\": 8, \"disp\": 360, \"hp\": 175, \"drat\": 3.15, \"wt\": 3.44, \"qsec\": 17.02, \"vs\": 0, \"am\": 0, \"gear\": 3, \"carb\": 2 },\n { \"name\": \"Valiant\", \"mpg\": 18.1, \"cyl\": 6, \"disp\": 225, \"hp\": 105, \"drat\": 2.76, \"wt\": 3.46, \"qsec\": 20.22, \"vs\": 1, \"am\": 0, \"gear\": 3, \"carb\": 1 },\n { \"name\": \"Duster 360\", \"mpg\": 14.3, \"cyl\": 8, \"disp\": 360, \"hp\": 245, \"drat\": 3.21, \"wt\": 3.57, \"qsec\": 15.84, \"vs\": 0, \"am\": 0, \"gear\": 3, \"carb\": 4 },\n { \"name\": \"Merc 240D\", \"mpg\": 24.4, \"cyl\": 4, \"disp\": 146.7, \"hp\": 62, \"drat\": 3.69, \"wt\": 3.19, \"qsec\": 20, \"vs\": 1, \"am\": 0, \"gear\": 4, \"carb\": 2 },\n { \"name\": \"Merc 230\", \"mpg\": 22.8, \"cyl\": 4, \"disp\": 140.8, \"hp\": 95, \"drat\": 3.92, \"wt\": 3.15, \"qsec\": 22.9, \"vs\": 1, \"am\": 0, \"gear\": 4, \"carb\": 2 },\n { \"name\": \"Merc 280\", \"mpg\": 19.2, \"cyl\": 6, \"disp\": 167.6, \"hp\": 123, \"drat\": 3.92, \"wt\": 3.44, \"qsec\": 18.3, \"vs\": 1, \"am\": 0, \"gear\": 4, \"carb\": 4 },\n { \"name\": \"Merc 280C\", \"mpg\": 17.8, \"cyl\": 6, \"disp\": 167.6, \"hp\": 123, \"drat\": 3.92, \"wt\": 3.44, \"qsec\": 18.9, \"vs\": 1, \"am\": 0, \"gear\": 4, \"carb\": 4 },\n { \"name\": \"Merc 450SE\", \"mpg\": 16.4, \"cyl\": 8, \"disp\": 275.8, \"hp\": 180, \"drat\": 3.07, \"wt\": 4.07, \"qsec\": 17.4, \"vs\": 0, \"am\": 0, \"gear\": 3, \"carb\": 3 },\n { \"name\": \"Merc 450SL\", \"mpg\": 17.3, \"cyl\": 8, \"disp\": 275.8, \"hp\": 180, \"drat\": 3.07, \"wt\": 3.73, \"qsec\": 17.6, \"vs\": 0, \"am\": 0, \"gear\": 3, \"carb\": 3 },\n { \"name\": \"Merc 450SLC\", \"mpg\": 15.2, \"cyl\": 8, \"disp\": 275.8, \"hp\": 180, \"drat\": 3.07, \"wt\": 3.78, \"qsec\": 18, \"vs\": 0, \"am\": 0, \"gear\": 3, \"carb\": 3 },\n { \"name\": \"Cadillac Fleetwood\", \"mpg\": 10.4, \"cyl\": 8, \"disp\": 472, \"hp\": 205, \"drat\": 2.93, \"wt\": 5.25, \"qsec\": 17.98, \"vs\": 0, \"am\": 0, \"gear\": 3, \"carb\": 4 },\n { \"name\": \"Lincoln Continental\", \"mpg\": 10.4, \"cyl\": 8, \"disp\": 460, \"hp\": 215, \"drat\": 3, \"wt\": 5.424, \"qsec\": 17.82, \"vs\": 0, \"am\": 0, \"gear\": 3, \"carb\": 4 },\n { \"name\": \"Chrysler Imperial\", \"mpg\": 14.7, \"cyl\": 8, \"disp\": 440, \"hp\": 230, \"drat\": 3.23, \"wt\": 5.345, \"qsec\": 17.42, \"vs\": 0, \"am\": 0, \"gear\": 3, \"carb\": 4 },\n { \"name\": \"Fiat 128\", \"mpg\": 32.4, \"cyl\": 4, \"disp\": 78.7, \"hp\": 66, \"drat\": 4.08, \"wt\": 2.2, \"qsec\": 19.47, \"vs\": 1, \"am\": 1, \"gear\": 4, \"carb\": 1 },\n { \"name\": \"Honda Civic\", \"mpg\": 30.4, \"cyl\": 4, \"disp\": 75.7, \"hp\": 52, \"drat\": 4.93, \"wt\": 1.615, \"qsec\": 18.52, \"vs\": 1, \"am\": 1, \"gear\": 4, \"carb\": 2 },\n { \"name\": \"Toyota Corolla\", \"mpg\": 33.9, \"cyl\": 4, \"disp\": 71.1, \"hp\": 65, \"drat\": 4.22, \"wt\": 1.835, \"qsec\": 19.9, \"vs\": 1, \"am\": 1, \"gear\": 4, \"carb\": 1 },\n { \"name\": \"Toyota Corona\", \"mpg\": 21.5, \"cyl\": 4, \"disp\": 120.1, \"hp\": 97, \"drat\": 3.7, \"wt\": 2.465, \"qsec\": 20.01, \"vs\": 1, \"am\": 0, \"gear\": 3, \"carb\": 1 },\n { \"name\": \"Dodge Challenger\", \"mpg\": 15.5, \"cyl\": 8, \"disp\": 318, \"hp\": 150, \"drat\": 2.76, \"wt\": 3.52, \"qsec\": 16.87, \"vs\": 0, \"am\": 0, \"gear\": 3, \"carb\": 2 },\n { \"name\": \"AMC Javelin\", \"mpg\": 15.2, \"cyl\": 8, \"disp\": 304, \"hp\": 150, \"drat\": 3.15, \"wt\": 3.435, \"qsec\": 17.3, \"vs\": 0, \"am\": 0, \"gear\": 3, \"carb\": 2 },\n { \"name\": \"Camaro Z28\", \"mpg\": 13.3, \"cyl\": 8, \"disp\": 350, \"hp\": 245, \"drat\": 3.73, \"wt\": 3.84, \"qsec\": 15.41, \"vs\": 0, \"am\": 0, \"gear\": 3, \"carb\": 4 },\n { \"name\": \"Pontiac Firebird\", \"mpg\": 19.2, \"cyl\": 8, \"disp\": 400, \"hp\": 175, \"drat\": 3.08, \"wt\": 3.845, \"qsec\": 17.05, \"vs\": 0, \"am\": 0, \"gear\": 3, \"carb\": 2 },\n { \"name\": \"Fiat X1-9\", \"mpg\": 27.3, \"cyl\": 4, \"disp\": 79, \"hp\": 66, \"drat\": 4.08, \"wt\": 1.935, \"qsec\": 18.9, \"vs\": 1, \"am\": 1, \"gear\": 4, \"carb\": 1 },\n { \"name\": \"Porsche 914-2\", \"mpg\": 26, \"cyl\": 4, \"disp\": 120.3, \"hp\": 91, \"drat\": 4.43, \"wt\": 2.14, \"qsec\": 16.7, \"vs\": 0, \"am\": 1, \"gear\": 5, \"carb\": 2 },\n { \"name\": \"Lotus Europa\", \"mpg\": 30.4, \"cyl\": 4, \"disp\": 95.1, \"hp\": 113, \"drat\": 3.77, \"wt\": 1.513, \"qsec\": 16.9, \"vs\": 1, \"am\": 1, \"gear\": 5, \"carb\": 2 },\n { \"name\": \"Ford Pantera L\", \"mpg\": 15.8, \"cyl\": 8, \"disp\": 351, \"hp\": 264, \"drat\": 4.22, \"wt\": 3.17, \"qsec\": 14.5, \"vs\": 0, \"am\": 1, \"gear\": 5, \"carb\": 4 },\n { \"name\": \"Ferrari Dino\", \"mpg\": 19.7, \"cyl\": 6, \"disp\": 145, \"hp\": 175, \"drat\": 3.62, \"wt\": 2.77, \"qsec\": 15.5, \"vs\": 0, \"am\": 1, \"gear\": 5, \"carb\": 6 },\n { \"name\": \"Maserati Bora\", \"mpg\": 15, \"cyl\": 8, \"disp\": 301, \"hp\": 335, \"drat\": 3.54, \"wt\": 3.57, \"qsec\": 14.6, \"vs\": 0, \"am\": 1, \"gear\": 5, \"carb\": 8 },\n { \"name\": \"Volvo 142E\", \"mpg\": 21.4, \"cyl\": 4, \"disp\": 121, \"hp\": 109, \"drat\": 4.11, \"wt\": 2.78, \"qsec\": 18.6, \"vs\": 1, \"am\": 1, \"gear\": 4, \"carb\": 2 }\n]", 765 | "pinned": false, 766 | "mode": "js", 767 | "data": null, 768 | "name": null 769 | }, 770 | { 771 | "id": 50, 772 | "value": "import {Table} from \"@observablehq/inputs\"", 773 | "pinned": true, 774 | "mode": "js", 775 | "data": null, 776 | "name": null 777 | }, 778 | { 779 | "id": 611, 780 | "value": "// switch from tmcw table to observablehq and just use same name\ntable = Table", 781 | "pinned": true, 782 | "mode": "js", 783 | "data": null, 784 | "name": null 785 | }, 786 | { 787 | "id": 61, 788 | "value": "table1csv = `country,year,cases,population\nAfghanistan,1999,745,19987071\nAfghanistan,2000,2666,20595360\nBrazil,1999,37737,172006362\nBrazil,2000,80488,174504898\nChina,1999,212258,1272915272\nChina,2000,213766,1280428583`", 789 | "pinned": true, 790 | "mode": "js", 791 | "data": null, 792 | "name": null 793 | }, 794 | { 795 | "id": 63, 796 | "value": "table1 = d3.csvParse(table1csv, d => ({ ...d, year: +d.year, cases: +d.cases, population: +d.population }))", 797 | "pinned": true, 798 | "mode": "js", 799 | "data": null, 800 | "name": null 801 | }, 802 | { 803 | "id": 73, 804 | "value": "table2csv = `country,year,type,count\nAfghanistan,1999,cases,745\nAfghanistan,1999,population,19987071\nAfghanistan,2000,cases,2666\nAfghanistan,2000,population,20595360\nBrazil,1999,cases,37737\nBrazil,1999,population,172006362\nBrazil,2000,cases,80488\nBrazil,2000,population,174504898\nChina,1999,cases,212258\nChina,1999,population,1272915272\nChina,2000,cases,213766\nChina,2000,population,1280428583`", 805 | "pinned": true, 806 | "mode": "js", 807 | "data": null, 808 | "name": null 809 | }, 810 | { 811 | "id": 75, 812 | "value": "table2 = d3.csvParse(table2csv, d => ({ ...d, year: +d.year, count: +d.count }))", 813 | "pinned": true, 814 | "mode": "js", 815 | "data": null, 816 | "name": null 817 | }, 818 | { 819 | "id": 80, 820 | "value": "table3csv=`country,year,rate\nAfghanistan,1999,745/19987071\nAfghanistan,2000,2666/20595360\nBrazil,1999,37737/172006362\nBrazil,2000,80488/174504898\nChina,1999,212258/1272915272\nChina,2000,213766/1280428583\n`", 821 | "pinned": true, 822 | "mode": "js", 823 | "data": null, 824 | "name": null 825 | }, 826 | { 827 | "id": 82, 828 | "value": "table3 = d3.csvParse(table3csv, d => ({ ...d, year: +d.year }))", 829 | "pinned": true, 830 | "mode": "js", 831 | "data": null, 832 | "name": null 833 | }, 834 | { 835 | "id": 84, 836 | "value": "table4acsv = `country,1999,2000\nAfghanistan,745,2666\nBrazil,37737,80488\nChina,212258,213766`", 837 | "pinned": true, 838 | "mode": "js", 839 | "data": null, 840 | "name": null 841 | }, 842 | { 843 | "id": 86, 844 | "value": "table4a = d3.csvParse(table4acsv, d => ({ ...d, '1999': +d['1999'], '2000': +d['2000'] }))", 845 | "pinned": true, 846 | "mode": "js", 847 | "data": null, 848 | "name": null 849 | }, 850 | { 851 | "id": 88, 852 | "value": "table4bcsv = `country,1999,2000\nAfghanistan,19987071,20595360\nBrazil,172006362,174504898\nChina,1272915272,1280428583`", 853 | "pinned": true, 854 | "mode": "js", 855 | "data": null, 856 | "name": null 857 | }, 858 | { 859 | "id": 90, 860 | "value": "table4b = d3.csvParse(table4bcsv, d => ({ ...d, '1999': +d['1999'], '2000': +d['2000'] }))", 861 | "pinned": true, 862 | "mode": "js", 863 | "data": null, 864 | "name": null 865 | }, 866 | { 867 | "id": 248, 868 | "value": "table5 = tidy(\n table3,\n mutate({\n century: d => `${Math.floor(d.year / 100)}`,\n year: d => `${d.year % 100}`.padStart(2, '0')\n }),\n select(['country', 'century', 'year', 'rate'])\n)", 869 | "pinned": true, 870 | "mode": "js", 871 | "data": null, 872 | "name": null 873 | }, 874 | { 875 | "id": 104, 876 | "value": "whocsv = await FileAttachment(\"who.csv\").text()", 877 | "pinned": true, 878 | "mode": "js", 879 | "data": null, 880 | "name": null 881 | }, 882 | { 883 | "id": 95, 884 | "value": "who = d3.csvParse(whocsv, d => ({ ...d, year: +d.year,}))", 885 | "pinned": true, 886 | "mode": "js", 887 | "data": null, 888 | "name": null 889 | }, 890 | { 891 | "id": 280, 892 | "value": "stocks = [\n { year: 2015, qtr: 1, return: 1.88 },\n { year: 2015, qtr: 2, return: 0.59 },\n { year: 2015, qtr: 3, return: 0.35 },\n { year: 2015, qtr: 4, return: null },\n { year: 2016, qtr: 2, return: 0.92 },\n { year: 2016, qtr: 3, return: 0.17 },\n { year: 2016, qtr: 4, return: 2.66 }\n]", 893 | "pinned": true, 894 | "mode": "js", 895 | "data": null, 896 | "name": null 897 | } 898 | ], 899 | "resolutions": 900 | [] 901 | }, 902 | "initialAutosave": true, 903 | "initialSafe": false, 904 | "initialCurrentUser": 905 | { 906 | "id": "4193fbe2c8b30fd3", 907 | "github_login": "hrbrmstr", 908 | "avatar_url": "https://avatars.observableusercontent.com/avatar/a26fc528815774e5c1e5ba21faf0a748a105234dd4c0fad8293732ef66c00f82", 909 | "login": "hrbrmstr", 910 | "name": "boB Rudis", 911 | "bio": "Don't look at me…I do what he does—just slower. #rstats avuncular • Chef • Cyclist • Christian • [Master] Chef des Données de Sécurité • #tired", 912 | "home_url": "http://rud.is/b", 913 | "email": "bob@rud.is", 914 | "google_email": null, 915 | "twitter_login": null, 916 | "create_time": "2018-01-31T16:08:14.734Z", 917 | "features": 918 | [ 919 | "curated_search_results" 920 | ], 921 | "settings": 922 | { 923 | "autoclose_pairs": "true", 924 | "default_cell_mode": "", 925 | "help_signpost": "3", 926 | "notebook_autonext": "true", 927 | "prettier": "true", 928 | "signpost_help": "hide", 929 | "signpost_insertmenu": "3", 930 | "spellcheck": "true", 931 | "tip_dismissed_welcome_v1": "Sun Apr 04 2021 08:27:43 GMT-0400 (Eastern Daylight Time)" 932 | }, 933 | "profile_email": "", 934 | "links": 935 | [], 936 | "selected_banner": null, 937 | "uploaded_banner": null, 938 | "microsoft_id": null, 939 | "otp_id": null, 940 | "pronouns": "", 941 | "tier": "basic", 942 | "delinquent": false, 943 | "teams": 944 | [], 945 | "identities": 946 | [ 947 | { 948 | "id": "cb913fe9716f99e8", 949 | "provider": "github", 950 | "provider_org": "", 951 | "login": "hrbrmstr", 952 | "name": "boB Rudis", 953 | "email": "bob@rud.is", 954 | "bio": "🇺🇦 Pampa • Don't look at me…I do what he does—just slower. #rstats avuncular•👨‍🍳•✝️• 💤• Varaforseti í Gögn Vísindi @ GreyNoise + CMU Lecturer #BLM", 955 | "avatar_url": "https://avatars.githubusercontent.com/u/509878?v=4", 956 | "home_url": "http://rud.is/b" 957 | } 958 | ] 959 | }, 960 | "initialContext": 961 | { 962 | "id": "26ab57b8a43a5dda", 963 | "github_login": "pbeshai", 964 | "avatar_url": "https://avatars.observableusercontent.com/avatar/db43a100f98fe00872d57f02fe3deb1fe4d5305239db9310ba983a9c7d6efb65", 965 | "login": "pbeshai", 966 | "name": "Peter Beshai", 967 | "bio": "", 968 | "home_url": "http://peterbeshai.com", 969 | "type": "individual", 970 | "tier": "basic" 971 | }, 972 | "initialCollection": 973 | { 974 | "id": "5610fa9dc3f1eab0", 975 | "type": "public", 976 | "slug": "tidy-js", 977 | "title": "tidy.js", 978 | "description": "Examples of using tidy.js to wrangle data", 979 | "update_time": "2021-02-24T22:18:57.641Z", 980 | "pinned": false, 981 | "ordered": false, 982 | "custom_thumbnail": null, 983 | "default_thumbnail": "42469726dbbe2a563c5e6e8e7e7dc36399a5d06ee2d97eec85c7c2b85fcef306", 984 | "thumbnail": "42469726dbbe2a563c5e6e8e7e7dc36399a5d06ee2d97eec85c7c2b85fcef306", 985 | "listing_count": 6, 986 | "owner": 987 | { 988 | "id": "26ab57b8a43a5dda", 989 | "github_login": "pbeshai", 990 | "avatar_url": "https://avatars.observableusercontent.com/avatar/db43a100f98fe00872d57f02fe3deb1fe4d5305239db9310ba983a9c7d6efb65", 991 | "login": "pbeshai", 992 | "name": "Peter Beshai", 993 | "bio": "", 994 | "home_url": "http://peterbeshai.com", 995 | "type": "individual", 996 | "tier": "basic" 997 | }, 998 | "listings": 999 | [ 1000 | { 1001 | "id": "a476ce385b75bc7f", 1002 | "type": "notebook_reader", 1003 | "publish_level": "public", 1004 | "version": 166, 1005 | "publish_version": 166, 1006 | "title": "tidy.js - zero-filling yearly bar charts with complete()", 1007 | "update_time": "2021-03-23T15:30:14.190Z", 1008 | "publish_time": "2021-03-23T15:30:14.190Z", 1009 | "likes": 1, 1010 | "comment_count": 0, 1011 | "slug": "tidy-js-zero-filling-yearly-bar-charts-with-complete", 1012 | "thumbnail": "42469726dbbe2a563c5e6e8e7e7dc36399a5d06ee2d97eec85c7c2b85fcef306", 1013 | "default_thumbnail": null, 1014 | "fork_of": false, 1015 | "collection_count": 1, 1016 | "roles": 1017 | [], 1018 | "authors": 1019 | [ 1020 | { 1021 | "id": "26ab57b8a43a5dda", 1022 | "avatar_url": "https://avatars.observableusercontent.com/avatar/db43a100f98fe00872d57f02fe3deb1fe4d5305239db9310ba983a9c7d6efb65", 1023 | "name": "Peter Beshai", 1024 | "login": "pbeshai", 1025 | "bio": "", 1026 | "home_url": "http://peterbeshai.com", 1027 | "github_login": "pbeshai", 1028 | "tier": "basic", 1029 | "approved": true, 1030 | "description": "" 1031 | } 1032 | ], 1033 | "sharing": null, 1034 | "notebook_description": "", 1035 | "listing_type": "notebook", 1036 | "owner": 1037 | { 1038 | "type": "individual", 1039 | "id": "26ab57b8a43a5dda", 1040 | "github_login": "pbeshai", 1041 | "login": "pbeshai", 1042 | "name": "Peter Beshai", 1043 | "bio": "", 1044 | "home_url": "http://peterbeshai.com", 1045 | "avatar_url": "https://avatars.observableusercontent.com/avatar/db43a100f98fe00872d57f02fe3deb1fe4d5305239db9310ba983a9c7d6efb65", 1046 | "tier": "basic" 1047 | }, 1048 | "creator": 1049 | { 1050 | "type": "individual", 1051 | "id": "26ab57b8a43a5dda", 1052 | "github_login": "pbeshai", 1053 | "login": "pbeshai", 1054 | "name": "Peter Beshai", 1055 | "bio": "", 1056 | "home_url": "http://peterbeshai.com", 1057 | "avatar_url": "https://avatars.observableusercontent.com/avatar/db43a100f98fe00872d57f02fe3deb1fe4d5305239db9310ba983a9c7d6efb65", 1058 | "tier": "basic" 1059 | } 1060 | }, 1061 | { 1062 | "id": "8b770ac8cd0fe392", 1063 | "type": "notebook_reader", 1064 | "publish_level": "public", 1065 | "version": 208, 1066 | "publish_version": 208, 1067 | "title": "tidy.js - converting data to percentages of a whole", 1068 | "update_time": "2021-05-07T19:19:58.215Z", 1069 | "publish_time": "2021-03-03T18:57:55.940Z", 1070 | "likes": 5, 1071 | "comment_count": 0, 1072 | "slug": "tidy-js-converting-data-to-percentages-of-a-whole", 1073 | "thumbnail": "0b420857867937133d983b7146fb758fa055cce1885d300f8c644ef5c9dcc575", 1074 | "default_thumbnail": null, 1075 | "fork_of": false, 1076 | "collection_count": 1, 1077 | "roles": 1078 | [], 1079 | "authors": 1080 | [ 1081 | { 1082 | "id": "26ab57b8a43a5dda", 1083 | "avatar_url": "https://avatars.observableusercontent.com/avatar/db43a100f98fe00872d57f02fe3deb1fe4d5305239db9310ba983a9c7d6efb65", 1084 | "name": "Peter Beshai", 1085 | "login": "pbeshai", 1086 | "bio": "", 1087 | "home_url": "http://peterbeshai.com", 1088 | "github_login": "pbeshai", 1089 | "tier": "basic", 1090 | "approved": true, 1091 | "description": "" 1092 | } 1093 | ], 1094 | "sharing": null, 1095 | "notebook_description": "", 1096 | "listing_type": "notebook", 1097 | "owner": 1098 | { 1099 | "type": "individual", 1100 | "id": "26ab57b8a43a5dda", 1101 | "github_login": "pbeshai", 1102 | "login": "pbeshai", 1103 | "name": "Peter Beshai", 1104 | "bio": "", 1105 | "home_url": "http://peterbeshai.com", 1106 | "avatar_url": "https://avatars.observableusercontent.com/avatar/db43a100f98fe00872d57f02fe3deb1fe4d5305239db9310ba983a9c7d6efb65", 1107 | "tier": "basic" 1108 | }, 1109 | "creator": 1110 | { 1111 | "type": "individual", 1112 | "id": "26ab57b8a43a5dda", 1113 | "github_login": "pbeshai", 1114 | "login": "pbeshai", 1115 | "name": "Peter Beshai", 1116 | "bio": "", 1117 | "home_url": "http://peterbeshai.com", 1118 | "avatar_url": "https://avatars.observableusercontent.com/avatar/db43a100f98fe00872d57f02fe3deb1fe4d5305239db9310ba983a9c7d6efb65", 1119 | "tier": "basic" 1120 | } 1121 | }, 1122 | { 1123 | "id": "13da1c9c6714778b", 1124 | "type": "notebook_reader", 1125 | "publish_level": "public", 1126 | "version": 70, 1127 | "publish_version": 70, 1128 | "title": "tidy.js - groupBy: summarize while keeping reference to items", 1129 | "update_time": "2021-05-07T19:19:29.247Z", 1130 | "publish_time": "2021-02-05T23:32:50.409Z", 1131 | "likes": 4, 1132 | "comment_count": 0, 1133 | "slug": "tidy-js-groupby-summarize-while-keeping-reference-to-items", 1134 | "thumbnail": "0ca929f0063e5917a8d9ffa891e295910ddcfaa08fbf896a45a7302848dfe483", 1135 | "default_thumbnail": null, 1136 | "fork_of": false, 1137 | "collection_count": 1, 1138 | "roles": 1139 | [], 1140 | "authors": 1141 | [ 1142 | { 1143 | "id": "26ab57b8a43a5dda", 1144 | "avatar_url": "https://avatars.observableusercontent.com/avatar/db43a100f98fe00872d57f02fe3deb1fe4d5305239db9310ba983a9c7d6efb65", 1145 | "name": "Peter Beshai", 1146 | "login": "pbeshai", 1147 | "bio": "", 1148 | "home_url": "http://peterbeshai.com", 1149 | "github_login": "pbeshai", 1150 | "tier": "basic", 1151 | "approved": true, 1152 | "description": "" 1153 | } 1154 | ], 1155 | "sharing": null, 1156 | "notebook_description": "", 1157 | "listing_type": "notebook", 1158 | "owner": 1159 | { 1160 | "type": "individual", 1161 | "id": "26ab57b8a43a5dda", 1162 | "github_login": "pbeshai", 1163 | "login": "pbeshai", 1164 | "name": "Peter Beshai", 1165 | "bio": "", 1166 | "home_url": "http://peterbeshai.com", 1167 | "avatar_url": "https://avatars.observableusercontent.com/avatar/db43a100f98fe00872d57f02fe3deb1fe4d5305239db9310ba983a9c7d6efb65", 1168 | "tier": "basic" 1169 | }, 1170 | "creator": 1171 | { 1172 | "type": "individual", 1173 | "id": "26ab57b8a43a5dda", 1174 | "github_login": "pbeshai", 1175 | "login": "pbeshai", 1176 | "name": "Peter Beshai", 1177 | "bio": "", 1178 | "home_url": "http://peterbeshai.com", 1179 | "avatar_url": "https://avatars.observableusercontent.com/avatar/db43a100f98fe00872d57f02fe3deb1fe4d5305239db9310ba983a9c7d6efb65", 1180 | "tier": "basic" 1181 | } 1182 | }, 1183 | { 1184 | "id": "eb159cd244d1afa5", 1185 | "type": "notebook_reader", 1186 | "publish_level": "public", 1187 | "version": 195, 1188 | "publish_version": 195, 1189 | "title": "tidyjs", 1190 | "update_time": "2021-09-24T00:47:53.194Z", 1191 | "publish_time": "2021-02-24T17:20:00.613Z", 1192 | "likes": 9, 1193 | "comment_count": 0, 1194 | "slug": "tidyjs", 1195 | "thumbnail": "432b20744a9e64498f7bd7cab85b79ea205e9ba919222cee61980d4d5e1e0123", 1196 | "default_thumbnail": null, 1197 | "fork_of": false, 1198 | "collection_count": 1, 1199 | "roles": 1200 | [], 1201 | "authors": 1202 | [ 1203 | { 1204 | "id": "26ab57b8a43a5dda", 1205 | "avatar_url": "https://avatars.observableusercontent.com/avatar/db43a100f98fe00872d57f02fe3deb1fe4d5305239db9310ba983a9c7d6efb65", 1206 | "name": "Peter Beshai", 1207 | "login": "pbeshai", 1208 | "bio": "", 1209 | "home_url": "http://peterbeshai.com", 1210 | "github_login": "pbeshai", 1211 | "tier": "basic", 1212 | "approved": true, 1213 | "description": "" 1214 | } 1215 | ], 1216 | "sharing": null, 1217 | "notebook_description": "", 1218 | "listing_type": "notebook", 1219 | "owner": 1220 | { 1221 | "type": "individual", 1222 | "id": "26ab57b8a43a5dda", 1223 | "github_login": "pbeshai", 1224 | "login": "pbeshai", 1225 | "name": "Peter Beshai", 1226 | "bio": "", 1227 | "home_url": "http://peterbeshai.com", 1228 | "avatar_url": "https://avatars.observableusercontent.com/avatar/db43a100f98fe00872d57f02fe3deb1fe4d5305239db9310ba983a9c7d6efb65", 1229 | "tier": "basic" 1230 | }, 1231 | "creator": 1232 | { 1233 | "type": "individual", 1234 | "id": "26ab57b8a43a5dda", 1235 | "github_login": "pbeshai", 1236 | "login": "pbeshai", 1237 | "name": "Peter Beshai", 1238 | "bio": "", 1239 | "home_url": "http://peterbeshai.com", 1240 | "avatar_url": "https://avatars.observableusercontent.com/avatar/db43a100f98fe00872d57f02fe3deb1fe4d5305239db9310ba983a9c7d6efb65", 1241 | "tier": "basic" 1242 | } 1243 | }, 1244 | { 1245 | "id": "bcaf8c209f1a283f", 1246 | "type": "notebook_reader", 1247 | "publish_level": "public", 1248 | "version": 645, 1249 | "publish_version": 645, 1250 | "title": "tidy.js – Intro & Demo", 1251 | "update_time": "2021-02-24T17:25:21.441Z", 1252 | "publish_time": "2021-02-02T16:36:21.176Z", 1253 | "likes": 108, 1254 | "comment_count": 1, 1255 | "slug": "tidy-js-intro-demo", 1256 | "thumbnail": "f4e0b02c026359561c55c81f4e5974b0a789a15152645a2ea5bb0313759463fe", 1257 | "default_thumbnail": null, 1258 | "fork_of": false, 1259 | "collection_count": 1, 1260 | "roles": 1261 | [], 1262 | "authors": 1263 | [ 1264 | { 1265 | "id": "26ab57b8a43a5dda", 1266 | "avatar_url": "https://avatars.observableusercontent.com/avatar/db43a100f98fe00872d57f02fe3deb1fe4d5305239db9310ba983a9c7d6efb65", 1267 | "name": "Peter Beshai", 1268 | "login": "pbeshai", 1269 | "bio": "", 1270 | "home_url": "http://peterbeshai.com", 1271 | "github_login": "pbeshai", 1272 | "tier": "basic", 1273 | "approved": true, 1274 | "description": "" 1275 | } 1276 | ], 1277 | "sharing": null, 1278 | "notebook_description": "", 1279 | "listing_type": "notebook", 1280 | "owner": 1281 | { 1282 | "type": "individual", 1283 | "id": "26ab57b8a43a5dda", 1284 | "github_login": "pbeshai", 1285 | "login": "pbeshai", 1286 | "name": "Peter Beshai", 1287 | "bio": "", 1288 | "home_url": "http://peterbeshai.com", 1289 | "avatar_url": "https://avatars.observableusercontent.com/avatar/db43a100f98fe00872d57f02fe3deb1fe4d5305239db9310ba983a9c7d6efb65", 1290 | "tier": "basic" 1291 | }, 1292 | "creator": 1293 | { 1294 | "type": "individual", 1295 | "id": "26ab57b8a43a5dda", 1296 | "github_login": "pbeshai", 1297 | "login": "pbeshai", 1298 | "name": "Peter Beshai", 1299 | "bio": "", 1300 | "home_url": "http://peterbeshai.com", 1301 | "avatar_url": "https://avatars.observableusercontent.com/avatar/db43a100f98fe00872d57f02fe3deb1fe4d5305239db9310ba983a9c7d6efb65", 1302 | "tier": "basic" 1303 | } 1304 | }, 1305 | { 1306 | "id": "44ccf22ff3418cd3", 1307 | "type": "notebook_reader", 1308 | "publish_level": "public", 1309 | "version": 200, 1310 | "publish_version": 200, 1311 | "title": "tidy.js - prep data for Multi-Line Charts", 1312 | "update_time": "2021-05-07T19:21:13.073Z", 1313 | "publish_time": "2021-02-24T19:18:46.147Z", 1314 | "likes": 20, 1315 | "comment_count": 1, 1316 | "slug": "tidy-js-prep-data-for-multi-line-charts", 1317 | "thumbnail": "c99b91984da61c9affbe9775f87da41bbe970644855d4079f2092c7210533e34", 1318 | "default_thumbnail": null, 1319 | "fork_of": false, 1320 | "collection_count": 1, 1321 | "roles": 1322 | [], 1323 | "authors": 1324 | [ 1325 | { 1326 | "id": "26ab57b8a43a5dda", 1327 | "avatar_url": "https://avatars.observableusercontent.com/avatar/db43a100f98fe00872d57f02fe3deb1fe4d5305239db9310ba983a9c7d6efb65", 1328 | "name": "Peter Beshai", 1329 | "login": "pbeshai", 1330 | "bio": "", 1331 | "home_url": "http://peterbeshai.com", 1332 | "github_login": "pbeshai", 1333 | "tier": "basic", 1334 | "approved": true, 1335 | "description": "" 1336 | } 1337 | ], 1338 | "sharing": null, 1339 | "notebook_description": "", 1340 | "listing_type": "notebook", 1341 | "owner": 1342 | { 1343 | "type": "individual", 1344 | "id": "26ab57b8a43a5dda", 1345 | "github_login": "pbeshai", 1346 | "login": "pbeshai", 1347 | "name": "Peter Beshai", 1348 | "bio": "", 1349 | "home_url": "http://peterbeshai.com", 1350 | "avatar_url": "https://avatars.observableusercontent.com/avatar/db43a100f98fe00872d57f02fe3deb1fe4d5305239db9310ba983a9c7d6efb65", 1351 | "tier": "basic" 1352 | }, 1353 | "creator": 1354 | { 1355 | "type": "individual", 1356 | "id": "26ab57b8a43a5dda", 1357 | "github_login": "pbeshai", 1358 | "login": "pbeshai", 1359 | "name": "Peter Beshai", 1360 | "bio": "", 1361 | "home_url": "http://peterbeshai.com", 1362 | "avatar_url": "https://avatars.observableusercontent.com/avatar/db43a100f98fe00872d57f02fe3deb1fe4d5305239db9310ba983a9c7d6efb65", 1363 | "tier": "basic" 1364 | } 1365 | } 1366 | ], 1367 | "parent_collections": 1368 | [] 1369 | } 1370 | }, 1371 | "__N_SSP": true 1372 | }, 1373 | "page": "/[at]/[...specifiers]", 1374 | "query": 1375 | { 1376 | "collection": "@pbeshai/tidy-js", 1377 | "at": "@pbeshai", 1378 | "specifiers": 1379 | [ 1380 | "@pbeshai", 1381 | "tidy-js-intro-demo" 1382 | ] 1383 | }, 1384 | "buildId": "P613zbLODaR0aqmjfqIgp", 1385 | "isFallback": false, 1386 | "gssp": true, 1387 | "scriptLoader": 1388 | [] 1389 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | use std::fs::File; 3 | use std::io::Write; 4 | 5 | use clap::Parser; 6 | 7 | use downloader::Downloader; 8 | 9 | mod observable_json; 10 | use observable_json::*; 11 | 12 | #[derive(Parser, Debug)] 13 | #[clap(author, version, about, long_about = None)] 14 | struct Args { 15 | 16 | /// an Observable notebook short reference ("@hrbrmstr/just-the-facts") or a full URL 17 | #[clap(long)] 18 | ohq_ref: String, 19 | 20 | /// directory to place Quarto project and files (will be created if it does not exist) 21 | #[clap(long)] 22 | output_dir: String, 23 | 24 | /// optional filename for the main Quarto document (will be taken from the slug in `ohq_ref`; e.g. "just-the-facts" from the example param) 25 | #[clap(long)] 26 | filename: Option, 27 | 28 | /// turn cell echo on in the Quarto document (default is to not echo) 29 | #[clap(long)] 30 | echo: bool, 31 | 32 | /// Print Notebook metadata during processing 33 | #[clap(long)] 34 | verbose: bool, 35 | 36 | } 37 | 38 | fn main() { 39 | 40 | // ensure we have arguments 41 | let args = Args::parse(); 42 | 43 | // make the speficied directory 44 | let output_dir = shellexpand::full(&args.output_dir).expect("Cannot parse/expand the provided directory path."); 45 | std::fs::create_dir_all(output_dir.to_string()).expect("Cannot create the speficied output directory."); 46 | 47 | // build the obshq URL if needed 48 | let obs_url = if args.ohq_ref.starts_with("@") { format!("https://observablehq.com/{}", args.ohq_ref) } else { args.ohq_ref }; 49 | 50 | // start the scraper 51 | let client = reqwest::blocking::Client::new(); 52 | let response = client 53 | .get(obs_url.clone()) 54 | .send().expect("Error retrieving Notebook.") 55 | .text().expect("Error extracting body from GET request response."); 56 | 57 | let document = scraper::Html::parse_document(&response); 58 | 59 | // this is where they hide the actual notebook code 60 | let cell_selector = scraper::Selector::parse("script#__NEXT_DATA__").unwrap(); 61 | 62 | let mut cells = document.select(&cell_selector).map(|x| x.inner_html()); 63 | let cell = cells.next().expect("No Observable data block found."); 64 | 65 | // did they give us a filename? 66 | let filename_option = args.filename; 67 | 68 | // I likely need to trim down this and setup a separate job to monitor the schema 69 | let model: ObservableData = serde_json::from_str(&cell) 70 | .expect("It appears the JSON notebook schema has changed; please file an issue @ https://github.com/hrbrmstr/ohq2quarto/issues"); 71 | 72 | // TODO there is a plethora of metadata here that we can shove into the YAML 73 | let nb = model.props.page_props.initial_notebook; 74 | let nodes = nb.nodes; 75 | let title = nb.title; 76 | 77 | let authors: Vec = nb.authors.iter() 78 | .map(|author| author.name.clone()) 79 | .collect(); 80 | 81 | if args.verbose { 82 | println!(" Title: {}", title); 83 | println!(" Slug: {}", nb.slug); 84 | println!(" Author(s): {}", authors.join(",")); 85 | if nb.description != "" { 86 | println!("Description: {}", nb.description); 87 | } 88 | println!(" Copyright: {}", nb.copyright); 89 | if let Some(l) = nb.license { 90 | println!(" License: {}", l); 91 | } 92 | println!(" Observable: {}", obs_url); 93 | } 94 | 95 | // make a filename from the notebook slug if the caller did not specify one 96 | let qmd_file: String = if let Some(x) = &filename_option { 97 | x.clone() 98 | } else { 99 | format!("{}.qmd", nb.slug) 100 | }; 101 | 102 | // make _quarto.yml 103 | let file_path = Path::new(output_dir.as_ref()).join("_quarto.yml"); 104 | let mut prj = File::create(file_path).expect("Error opening _quarto.yml file for writing."); 105 | writeln!(prj, "project:").unwrap(); 106 | writeln!(prj, " title: {}", title).unwrap(); 107 | 108 | // make the qmd file 109 | let file_path = Path::new(output_dir.as_ref()).join(qmd_file); 110 | let mut qmd = File::create(file_path).expect("Error opening qmd file for writing."); 111 | 112 | writeln!(qmd, "---").unwrap(); 113 | writeln!(qmd, "title: '{}'", title).unwrap(); 114 | writeln!(qmd, "author: '{}'", authors.join(",")).unwrap(); 115 | writeln!(qmd, "format: html").unwrap(); 116 | writeln!(qmd, "echo: {}", args.echo).unwrap(); 117 | writeln!(qmd, "observable: '{}'", obs_url).unwrap(); 118 | writeln!(qmd, "---").unwrap(); 119 | writeln!(qmd).unwrap(); 120 | 121 | for node in nodes { 122 | writeln!(qmd, "```{{ojs}}").unwrap(); 123 | match node.mode.as_str() { 124 | "md" => writeln!(qmd, "md`{}`", node.value).unwrap(), 125 | "html" => writeln!(qmd, "html`{}`", node.value).unwrap(), 126 | _ => writeln!(qmd, "{}", node.value).unwrap() 127 | }; 128 | writeln!(qmd, "```").unwrap(); 129 | writeln!(qmd).unwrap(); 130 | } 131 | 132 | // Check to see if we need to download any FileAttachments 133 | if let Some(files) = nb.files { 134 | 135 | let output_dir = Path::new(output_dir.as_ref()); 136 | 137 | let mut downloader = Downloader::builder() 138 | .download_folder(output_dir) 139 | .parallel_requests(1) 140 | .build() 141 | .expect("Error setting up file downloader"); 142 | 143 | for file in files { 144 | 145 | let download_file = downloader::Download::new(file.url.as_ref()) 146 | .file_name(file.name.as_ref()); 147 | 148 | downloader.download(&[download_file]) 149 | .expect("Error downloading one of the FileAttachments"); 150 | 151 | }; 152 | 153 | }; 154 | 155 | } 156 | -------------------------------------------------------------------------------- /src/observable_json.rs: -------------------------------------------------------------------------------- 1 | // TODO thin this out more so we avoid unnecessary deserialization errors 2 | 3 | use serde_derive::{ Serialize, Deserialize}; 4 | #[derive(Debug, Serialize, Deserialize)] 5 | pub struct ObservableData { 6 | #[serde(rename = "props")] 7 | pub props: Props, 8 | 9 | // #[serde(rename = "page")] 10 | // pub page: String, 11 | 12 | // #[serde(rename = "query")] 13 | // pub query: Query, 14 | 15 | // #[serde(rename = "buildId")] 16 | // pub build_id: String, 17 | 18 | // #[serde(rename = "isFallback")] 19 | // pub is_fallback: bool, 20 | 21 | // #[serde(rename = "gssp")] 22 | // pub gssp: bool, 23 | 24 | // #[serde(rename = "scriptLoader")] 25 | // pub script_loader: Vec>, 26 | } 27 | 28 | #[derive(Debug, Serialize, Deserialize)] 29 | pub struct Props { 30 | #[serde(rename = "pageProps")] 31 | pub page_props: PageProps, 32 | 33 | // #[serde(rename = "__N_SSP")] 34 | // pub n_ssp: bool, 35 | } 36 | 37 | #[derive(Debug, Serialize, Deserialize)] 38 | pub struct PageProps { 39 | #[serde(rename = "initialNotebook")] 40 | pub initial_notebook: InitialNotebook, 41 | 42 | // #[serde(rename = "initialAutosave")] 43 | // pub initial_autosave: bool, 44 | 45 | // #[serde(rename = "initialSafe")] 46 | // pub initial_safe: bool, 47 | 48 | // #[serde(rename = "initialCurrentUser")] 49 | // pub initial_current_user: Option, 50 | 51 | // #[serde(rename = "initialContext")] 52 | // pub initial_context: InitialContext, 53 | 54 | #[serde(rename = "initialCollection")] 55 | pub initial_collection: Option, 56 | 57 | } 58 | 59 | 60 | #[derive(Debug, Serialize, Deserialize)] 61 | pub struct Collection { 62 | #[serde(rename = "id")] 63 | pub id: String, 64 | 65 | #[serde(rename = "type")] 66 | pub collection_type: String, 67 | 68 | #[serde(rename = "slug")] 69 | pub slug: String, 70 | 71 | #[serde(rename = "title")] 72 | pub title: String, 73 | 74 | #[serde(rename = "description")] 75 | pub description: String, 76 | 77 | #[serde(rename = "update_time")] 78 | pub update_time: String, 79 | 80 | #[serde(rename = "pinned")] 81 | pub pinned: bool, 82 | 83 | #[serde(rename = "ordered")] 84 | pub ordered: bool, 85 | 86 | #[serde(rename = "custom_thumbnail")] 87 | pub custom_thumbnail: Option, 88 | 89 | #[serde(rename = "default_thumbnail")] 90 | pub default_thumbnail: String, 91 | 92 | #[serde(rename = "thumbnail")] 93 | pub thumbnail: String, 94 | 95 | #[serde(rename = "listing_count")] 96 | pub listing_count: i64, 97 | 98 | // #[serde(rename = "owner")] 99 | // pub owner: InitialContext, 100 | 101 | #[serde(rename = "listings")] 102 | pub listings: Option>, 103 | 104 | #[serde(rename = "parent_collections")] 105 | pub parent_collections: Option>>, 106 | 107 | #[serde(rename = "parent_collection_count")] 108 | pub parent_collection_count: Option, 109 | } 110 | 111 | 112 | #[derive(Debug, Serialize, Deserialize)] 113 | pub struct Listing { 114 | #[serde(rename = "id")] 115 | pub id: String, 116 | 117 | #[serde(rename = "type")] 118 | pub purple_type: String, 119 | 120 | #[serde(rename = "publish_level")] 121 | pub publish_level: String, 122 | 123 | #[serde(rename = "version")] 124 | pub version: i64, 125 | 126 | #[serde(rename = "publish_version")] 127 | pub publish_version: i64, 128 | 129 | #[serde(rename = "title")] 130 | pub title: String, 131 | 132 | #[serde(rename = "update_time")] 133 | pub update_time: String, 134 | 135 | #[serde(rename = "publish_time")] 136 | pub publish_time: String, 137 | 138 | #[serde(rename = "likes")] 139 | pub likes: i64, 140 | 141 | #[serde(rename = "comment_count")] 142 | pub comment_count: i64, 143 | 144 | #[serde(rename = "slug")] 145 | pub slug: String, 146 | 147 | #[serde(rename = "thumbnail")] 148 | pub thumbnail: String, 149 | 150 | #[serde(rename = "default_thumbnail")] 151 | pub default_thumbnail: Option, 152 | 153 | #[serde(rename = "fork_of")] 154 | pub fork_of: bool, 155 | 156 | #[serde(rename = "collection_count")] 157 | pub collection_count: i64, 158 | 159 | #[serde(rename = "roles")] 160 | pub roles: Vec>, 161 | 162 | // #[serde(rename = "authors")] 163 | // pub authors: Vec, 164 | 165 | #[serde(rename = "sharing")] 166 | pub sharing: Option, 167 | 168 | #[serde(rename = "notebook_description")] 169 | pub notebook_description: String, 170 | 171 | #[serde(rename = "listing_type")] 172 | pub listing_type: String, 173 | 174 | // #[serde(rename = "owner")] 175 | // pub owner: InitialContext, 176 | 177 | // #[serde(rename = "creator")] 178 | // pub creator: InitialContext, 179 | } 180 | 181 | 182 | #[derive(Debug, Serialize, Deserialize)] 183 | pub struct InitialContext { 184 | #[serde(rename = "id")] 185 | pub id: String, 186 | 187 | // #[serde(rename = "github_login")] 188 | // pub github_login: String, 189 | 190 | // #[serde(rename = "avatar_url")] 191 | // pub avatar_url: String, 192 | 193 | // #[serde(rename = "login")] 194 | // pub login: String, 195 | 196 | #[serde(rename = "name")] 197 | pub name: String, 198 | 199 | // #[serde(rename = "bio")] 200 | // pub bio: String, 201 | 202 | // #[serde(rename = "home_url")] 203 | // pub home_url: String, 204 | 205 | // #[serde(rename = "type")] 206 | // pub initial_context_type: Option, 207 | 208 | // #[serde(rename = "tier")] 209 | // pub tier: String, 210 | 211 | // #[serde(rename = "approved")] 212 | // pub approved: Option, 213 | 214 | // #[serde(rename = "description")] 215 | // pub description: Option, 216 | } 217 | 218 | #[derive(Debug, Serialize, Deserialize)] 219 | pub struct InitialNotebook { 220 | // #[serde(rename = "id")] 221 | // pub id: String, 222 | 223 | #[serde(rename = "slug")] 224 | pub slug: String, 225 | 226 | // #[serde(rename = "trashed")] 227 | // pub trashed: bool, 228 | 229 | #[serde(rename = "description")] 230 | pub description: String, 231 | 232 | // #[serde(rename = "likes")] 233 | // pub likes: i64, 234 | 235 | // #[serde(rename = "publish_level")] 236 | // pub publish_level: String, 237 | 238 | // #[serde(rename = "forks")] 239 | // pub forks: i64, 240 | 241 | // #[serde(rename = "fork_of")] 242 | // pub fork_of: Option, 243 | 244 | // #[serde(rename = "update_time")] 245 | // pub update_time: String, 246 | 247 | // #[serde(rename = "publish_time")] 248 | // pub publish_time: String, 249 | 250 | // #[serde(rename = "publish_version")] 251 | // pub publish_version: i64, 252 | 253 | // #[serde(rename = "latest_version")] 254 | // pub latest_version: i64, 255 | 256 | // #[serde(rename = "thumbnail")] 257 | // pub thumbnail: String, 258 | 259 | // #[serde(rename = "default_thumbnail")] 260 | // pub default_thumbnail: String, 261 | 262 | // #[serde(rename = "roles")] 263 | // pub roles: Vec>, 264 | 265 | // #[serde(rename = "sharing")] 266 | // pub sharing: Option, 267 | 268 | // #[serde(rename = "owner")] 269 | // pub owner: InitialContext, 270 | 271 | // #[serde(rename = "creator")] 272 | // pub creator: InitialContext, 273 | 274 | #[serde(rename = "authors")] 275 | pub authors: Vec, 276 | 277 | // #[serde(rename = "collections")] 278 | // pub collections: Vec>, 279 | 280 | #[serde(rename = "files")] 281 | pub files: Option>, 282 | 283 | // #[serde(rename = "comments")] 284 | // pub comments: Vec>, 285 | 286 | // #[serde(rename = "commenting_lock")] 287 | // pub commenting_lock: Option, 288 | 289 | // #[serde(rename = "suggestion_from")] 290 | // pub suggestion_from: Option, 291 | 292 | // #[serde(rename = "suggestions_to")] 293 | // pub suggestions_to: Vec>, 294 | 295 | #[serde(rename = "version")] 296 | pub version: i64, 297 | 298 | #[serde(rename = "title")] 299 | pub title: String, 300 | 301 | #[serde(rename = "license")] 302 | pub license: Option, 303 | 304 | #[serde(rename = "copyright")] 305 | pub copyright: String, 306 | 307 | #[serde(rename = "nodes")] 308 | pub nodes: Vec, 309 | 310 | // #[serde(rename = "resolutions")] 311 | // pub resolutions: Vec, 312 | } 313 | 314 | #[derive(Debug, Serialize, Deserialize)] 315 | pub struct File { 316 | #[serde(rename = "id")] 317 | pub id: String, 318 | 319 | #[serde(rename = "url")] 320 | pub url: String, 321 | 322 | #[serde(rename = "download_url")] 323 | pub download_url: String, 324 | 325 | #[serde(rename = "name")] 326 | pub name: String, 327 | 328 | #[serde(rename = "create_time")] 329 | pub create_time: String, 330 | 331 | #[serde(rename = "status")] 332 | pub status: String, 333 | 334 | #[serde(rename = "size")] 335 | pub size: i64, 336 | 337 | #[serde(rename = "mime_type")] 338 | pub mime_type: String, 339 | 340 | #[serde(rename = "content_encoding")] 341 | pub content_encoding: Option, 342 | } 343 | 344 | #[derive(Debug, Serialize, Deserialize)] 345 | pub struct Node { 346 | #[serde(rename = "id")] 347 | pub id: i64, 348 | 349 | #[serde(rename = "value")] 350 | pub value: String, 351 | 352 | #[serde(rename = "pinned")] 353 | pub pinned: bool, 354 | 355 | #[serde(rename = "mode")] 356 | pub mode: String, 357 | 358 | #[serde(rename = "data")] 359 | pub data: Option, 360 | 361 | #[serde(rename = "name")] 362 | pub name: Option, 363 | } 364 | 365 | #[derive(Debug, Serialize, Deserialize)] 366 | pub struct Resolution { 367 | #[serde(rename = "type")] 368 | pub resolution_type: String, 369 | 370 | #[serde(rename = "specifier")] 371 | pub specifier: String, 372 | 373 | #[serde(rename = "value")] 374 | pub value: String, 375 | } 376 | 377 | #[derive(Debug, Serialize, Deserialize)] 378 | pub struct Query { 379 | #[serde(rename = "at")] 380 | pub at: String, 381 | 382 | #[serde(rename = "specifiers")] 383 | pub specifiers: Vec, 384 | } 385 | --------------------------------------------------------------------------------