├── .envrc ├── .github ├── FUNDING.yml └── workflows │ └── vhs.yaml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── flake.lock ├── flake.nix ├── meta └── vhs.gif ├── src ├── app.rs ├── config │ ├── moccasin.toml │ ├── mod.rs │ └── theme.rs ├── event.rs ├── feed │ ├── html.rs │ └── mod.rs ├── handler.rs ├── lib.rs ├── main.rs ├── repo │ ├── mod.rs │ ├── repo.rs │ └── storage │ │ ├── mod.rs │ │ ├── schema.sql │ │ └── sqlite.rs ├── tui.rs ├── ui │ ├── browse.rs │ ├── detail.rs │ ├── mod.rs │ └── themed.rs └── util.rs └── vhs.tape /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [rektdeckard] 2 | -------------------------------------------------------------------------------- /.github/workflows/vhs.yaml: -------------------------------------------------------------------------------- 1 | name: VHS 2 | on: [pull_request] 3 | jobs: 4 | vhs: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v4 8 | - name: Install moccasin 9 | run: cargo install --path . 10 | - uses: charmbracelet/vhs-action@v2 11 | with: 12 | path: "vhs.tape" 13 | - uses: stefanzweifel/git-auto-commit-action@v5 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | with: 17 | commit_message: "chore(ci): generate gif" 18 | commit_user_name: vhs-action 📼 19 | commit_user_email: actions@github.com 20 | commit_author: vhs-action 📼 21 | file_pattern: "*.gif" 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .direnv 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "ahash" 22 | version = "0.8.11" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 25 | dependencies = [ 26 | "cfg-if", 27 | "getrandom", 28 | "once_cell", 29 | "version_check", 30 | "zerocopy", 31 | ] 32 | 33 | [[package]] 34 | name = "aho-corasick" 35 | version = "1.1.3" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 38 | dependencies = [ 39 | "memchr", 40 | ] 41 | 42 | [[package]] 43 | name = "allocator-api2" 44 | version = "0.2.18" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" 47 | 48 | [[package]] 49 | name = "android-tzdata" 50 | version = "0.1.1" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 53 | 54 | [[package]] 55 | name = "android_system_properties" 56 | version = "0.1.5" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 59 | dependencies = [ 60 | "libc", 61 | ] 62 | 63 | [[package]] 64 | name = "anstream" 65 | version = "0.6.15" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" 68 | dependencies = [ 69 | "anstyle", 70 | "anstyle-parse", 71 | "anstyle-query", 72 | "anstyle-wincon", 73 | "colorchoice", 74 | "is_terminal_polyfill", 75 | "utf8parse", 76 | ] 77 | 78 | [[package]] 79 | name = "anstyle" 80 | version = "1.0.8" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" 83 | 84 | [[package]] 85 | name = "anstyle-parse" 86 | version = "0.2.5" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" 89 | dependencies = [ 90 | "utf8parse", 91 | ] 92 | 93 | [[package]] 94 | name = "anstyle-query" 95 | version = "1.1.1" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" 98 | dependencies = [ 99 | "windows-sys 0.52.0", 100 | ] 101 | 102 | [[package]] 103 | name = "anstyle-wincon" 104 | version = "3.0.4" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" 107 | dependencies = [ 108 | "anstyle", 109 | "windows-sys 0.52.0", 110 | ] 111 | 112 | [[package]] 113 | name = "anyhow" 114 | version = "1.0.89" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" 117 | 118 | [[package]] 119 | name = "atom_syndication" 120 | version = "0.12.4" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "2a3a5ed3201df5658d1aa45060c5a57dc9dba8a8ada20d696d67cb0c479ee043" 123 | dependencies = [ 124 | "chrono", 125 | "derive_builder", 126 | "diligent-date-parser", 127 | "never", 128 | "quick-xml", 129 | ] 130 | 131 | [[package]] 132 | name = "atomic" 133 | version = "0.6.0" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" 136 | dependencies = [ 137 | "bytemuck", 138 | ] 139 | 140 | [[package]] 141 | name = "autocfg" 142 | version = "1.4.0" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 145 | 146 | [[package]] 147 | name = "backtrace" 148 | version = "0.3.74" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 151 | dependencies = [ 152 | "addr2line", 153 | "cfg-if", 154 | "libc", 155 | "miniz_oxide", 156 | "object", 157 | "rustc-demangle", 158 | "windows-targets 0.52.6", 159 | ] 160 | 161 | [[package]] 162 | name = "base64" 163 | version = "0.13.1" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 166 | 167 | [[package]] 168 | name = "base64" 169 | version = "0.21.7" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 172 | 173 | [[package]] 174 | name = "bitflags" 175 | version = "1.3.2" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 178 | 179 | [[package]] 180 | name = "bitflags" 181 | version = "2.6.0" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 184 | 185 | [[package]] 186 | name = "bitvec" 187 | version = "1.0.1" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" 190 | dependencies = [ 191 | "funty", 192 | "radium", 193 | "tap", 194 | "wyz", 195 | ] 196 | 197 | [[package]] 198 | name = "block-buffer" 199 | version = "0.10.4" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 202 | dependencies = [ 203 | "generic-array", 204 | ] 205 | 206 | [[package]] 207 | name = "bson" 208 | version = "2.13.0" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "068208f2b6fcfa27a7f1ee37488d2bb8ba2640f68f5475d08e1d9130696aba59" 211 | dependencies = [ 212 | "ahash", 213 | "base64 0.13.1", 214 | "bitvec", 215 | "hex", 216 | "indexmap 2.6.0", 217 | "js-sys", 218 | "once_cell", 219 | "rand", 220 | "serde", 221 | "serde_bytes", 222 | "serde_json", 223 | "time", 224 | "uuid", 225 | ] 226 | 227 | [[package]] 228 | name = "bumpalo" 229 | version = "3.16.0" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 232 | 233 | [[package]] 234 | name = "bytemuck" 235 | version = "1.19.0" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" 238 | 239 | [[package]] 240 | name = "byteorder" 241 | version = "1.5.0" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 244 | 245 | [[package]] 246 | name = "bytes" 247 | version = "1.7.2" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" 250 | 251 | [[package]] 252 | name = "cassowary" 253 | version = "0.3.0" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" 256 | 257 | [[package]] 258 | name = "cc" 259 | version = "1.1.30" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" 262 | dependencies = [ 263 | "shlex", 264 | ] 265 | 266 | [[package]] 267 | name = "cfg-if" 268 | version = "1.0.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 271 | 272 | [[package]] 273 | name = "chrono" 274 | version = "0.4.38" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" 277 | dependencies = [ 278 | "android-tzdata", 279 | "iana-time-zone", 280 | "js-sys", 281 | "num-traits", 282 | "wasm-bindgen", 283 | "windows-targets 0.52.6", 284 | ] 285 | 286 | [[package]] 287 | name = "clap" 288 | version = "4.5.20" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" 291 | dependencies = [ 292 | "clap_builder", 293 | "clap_derive", 294 | ] 295 | 296 | [[package]] 297 | name = "clap_builder" 298 | version = "4.5.20" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" 301 | dependencies = [ 302 | "anstream", 303 | "anstyle", 304 | "clap_lex", 305 | "strsim", 306 | ] 307 | 308 | [[package]] 309 | name = "clap_derive" 310 | version = "4.5.18" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 313 | dependencies = [ 314 | "heck", 315 | "proc-macro2", 316 | "quote", 317 | "syn", 318 | ] 319 | 320 | [[package]] 321 | name = "clap_lex" 322 | version = "0.7.2" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" 325 | 326 | [[package]] 327 | name = "colorchoice" 328 | version = "1.0.2" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" 331 | 332 | [[package]] 333 | name = "colorsys" 334 | version = "0.6.7" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "54261aba646433cb567ec89844be4c4825ca92a4f8afba52fc4dd88436e31bbd" 337 | 338 | [[package]] 339 | name = "core-foundation" 340 | version = "0.9.4" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 343 | dependencies = [ 344 | "core-foundation-sys", 345 | "libc", 346 | ] 347 | 348 | [[package]] 349 | name = "core-foundation-sys" 350 | version = "0.8.7" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 353 | 354 | [[package]] 355 | name = "cpufeatures" 356 | version = "0.2.14" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" 359 | dependencies = [ 360 | "libc", 361 | ] 362 | 363 | [[package]] 364 | name = "crc64fast" 365 | version = "1.1.0" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "26bb92ecea20291efcf0009e2713d64b7e327dedb8ce780545250f24075429e2" 368 | 369 | [[package]] 370 | name = "crossterm" 371 | version = "0.26.1" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" 374 | dependencies = [ 375 | "bitflags 1.3.2", 376 | "crossterm_winapi", 377 | "libc", 378 | "mio 0.8.11", 379 | "parking_lot", 380 | "signal-hook", 381 | "signal-hook-mio", 382 | "winapi", 383 | ] 384 | 385 | [[package]] 386 | name = "crossterm" 387 | version = "0.27.0" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" 390 | dependencies = [ 391 | "bitflags 2.6.0", 392 | "crossterm_winapi", 393 | "libc", 394 | "mio 0.8.11", 395 | "parking_lot", 396 | "signal-hook", 397 | "signal-hook-mio", 398 | "winapi", 399 | ] 400 | 401 | [[package]] 402 | name = "crossterm_winapi" 403 | version = "0.9.1" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 406 | dependencies = [ 407 | "winapi", 408 | ] 409 | 410 | [[package]] 411 | name = "crypto-common" 412 | version = "0.1.6" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 415 | dependencies = [ 416 | "generic-array", 417 | "typenum", 418 | ] 419 | 420 | [[package]] 421 | name = "darling" 422 | version = "0.20.10" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" 425 | dependencies = [ 426 | "darling_core", 427 | "darling_macro", 428 | ] 429 | 430 | [[package]] 431 | name = "darling_core" 432 | version = "0.20.10" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" 435 | dependencies = [ 436 | "fnv", 437 | "ident_case", 438 | "proc-macro2", 439 | "quote", 440 | "strsim", 441 | "syn", 442 | ] 443 | 444 | [[package]] 445 | name = "darling_macro" 446 | version = "0.20.10" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" 449 | dependencies = [ 450 | "darling_core", 451 | "quote", 452 | "syn", 453 | ] 454 | 455 | [[package]] 456 | name = "deranged" 457 | version = "0.3.11" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 460 | dependencies = [ 461 | "powerfmt", 462 | ] 463 | 464 | [[package]] 465 | name = "derive_builder" 466 | version = "0.20.2" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" 469 | dependencies = [ 470 | "derive_builder_macro", 471 | ] 472 | 473 | [[package]] 474 | name = "derive_builder_core" 475 | version = "0.20.2" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" 478 | dependencies = [ 479 | "darling", 480 | "proc-macro2", 481 | "quote", 482 | "syn", 483 | ] 484 | 485 | [[package]] 486 | name = "derive_builder_macro" 487 | version = "0.20.2" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" 490 | dependencies = [ 491 | "derive_builder_core", 492 | "syn", 493 | ] 494 | 495 | [[package]] 496 | name = "digest" 497 | version = "0.10.7" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 500 | dependencies = [ 501 | "block-buffer", 502 | "crypto-common", 503 | ] 504 | 505 | [[package]] 506 | name = "diligent-date-parser" 507 | version = "0.1.4" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "f6cf7fe294274a222363f84bcb63cdea762979a0443b4cf1f4f8fd17c86b1182" 510 | dependencies = [ 511 | "chrono", 512 | ] 513 | 514 | [[package]] 515 | name = "directories" 516 | version = "5.0.1" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" 519 | dependencies = [ 520 | "dirs-sys", 521 | ] 522 | 523 | [[package]] 524 | name = "dirs-sys" 525 | version = "0.4.1" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" 528 | dependencies = [ 529 | "libc", 530 | "option-ext", 531 | "redox_users", 532 | "windows-sys 0.48.0", 533 | ] 534 | 535 | [[package]] 536 | name = "doc-comment" 537 | version = "0.3.3" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 540 | 541 | [[package]] 542 | name = "encoding_rs" 543 | version = "0.8.34" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" 546 | dependencies = [ 547 | "cfg-if", 548 | ] 549 | 550 | [[package]] 551 | name = "equivalent" 552 | version = "1.0.1" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 555 | 556 | [[package]] 557 | name = "errno" 558 | version = "0.3.9" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 561 | dependencies = [ 562 | "libc", 563 | "windows-sys 0.52.0", 564 | ] 565 | 566 | [[package]] 567 | name = "fallible-iterator" 568 | version = "0.2.0" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" 571 | 572 | [[package]] 573 | name = "fallible-streaming-iterator" 574 | version = "0.1.9" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" 577 | 578 | [[package]] 579 | name = "fastrand" 580 | version = "2.1.1" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" 583 | 584 | [[package]] 585 | name = "fnv" 586 | version = "1.0.7" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 589 | 590 | [[package]] 591 | name = "foreign-types" 592 | version = "0.3.2" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 595 | dependencies = [ 596 | "foreign-types-shared", 597 | ] 598 | 599 | [[package]] 600 | name = "foreign-types-shared" 601 | version = "0.1.1" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 604 | 605 | [[package]] 606 | name = "form_urlencoded" 607 | version = "1.2.1" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 610 | dependencies = [ 611 | "percent-encoding", 612 | ] 613 | 614 | [[package]] 615 | name = "funty" 616 | version = "2.0.0" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" 619 | 620 | [[package]] 621 | name = "futures" 622 | version = "0.3.31" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 625 | dependencies = [ 626 | "futures-channel", 627 | "futures-core", 628 | "futures-executor", 629 | "futures-io", 630 | "futures-sink", 631 | "futures-task", 632 | "futures-util", 633 | ] 634 | 635 | [[package]] 636 | name = "futures-channel" 637 | version = "0.3.31" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 640 | dependencies = [ 641 | "futures-core", 642 | "futures-sink", 643 | ] 644 | 645 | [[package]] 646 | name = "futures-core" 647 | version = "0.3.31" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 650 | 651 | [[package]] 652 | name = "futures-executor" 653 | version = "0.3.31" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 656 | dependencies = [ 657 | "futures-core", 658 | "futures-task", 659 | "futures-util", 660 | ] 661 | 662 | [[package]] 663 | name = "futures-io" 664 | version = "0.3.31" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 667 | 668 | [[package]] 669 | name = "futures-macro" 670 | version = "0.3.31" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 673 | dependencies = [ 674 | "proc-macro2", 675 | "quote", 676 | "syn", 677 | ] 678 | 679 | [[package]] 680 | name = "futures-sink" 681 | version = "0.3.31" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 684 | 685 | [[package]] 686 | name = "futures-task" 687 | version = "0.3.31" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 690 | 691 | [[package]] 692 | name = "futures-util" 693 | version = "0.3.31" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 696 | dependencies = [ 697 | "futures-channel", 698 | "futures-core", 699 | "futures-io", 700 | "futures-macro", 701 | "futures-sink", 702 | "futures-task", 703 | "memchr", 704 | "pin-project-lite", 705 | "pin-utils", 706 | "slab", 707 | ] 708 | 709 | [[package]] 710 | name = "generic-array" 711 | version = "0.14.7" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 714 | dependencies = [ 715 | "typenum", 716 | "version_check", 717 | ] 718 | 719 | [[package]] 720 | name = "getrandom" 721 | version = "0.2.15" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 724 | dependencies = [ 725 | "cfg-if", 726 | "js-sys", 727 | "libc", 728 | "wasi", 729 | "wasm-bindgen", 730 | ] 731 | 732 | [[package]] 733 | name = "gimli" 734 | version = "0.31.1" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 737 | 738 | [[package]] 739 | name = "h2" 740 | version = "0.3.26" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" 743 | dependencies = [ 744 | "bytes", 745 | "fnv", 746 | "futures-core", 747 | "futures-sink", 748 | "futures-util", 749 | "http", 750 | "indexmap 2.6.0", 751 | "slab", 752 | "tokio", 753 | "tokio-util", 754 | "tracing", 755 | ] 756 | 757 | [[package]] 758 | name = "hashbrown" 759 | version = "0.12.3" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 762 | 763 | [[package]] 764 | name = "hashbrown" 765 | version = "0.13.2" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" 768 | dependencies = [ 769 | "ahash", 770 | ] 771 | 772 | [[package]] 773 | name = "hashbrown" 774 | version = "0.14.5" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 777 | dependencies = [ 778 | "ahash", 779 | "allocator-api2", 780 | ] 781 | 782 | [[package]] 783 | name = "hashbrown" 784 | version = "0.15.0" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" 787 | 788 | [[package]] 789 | name = "hashlink" 790 | version = "0.8.4" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" 793 | dependencies = [ 794 | "hashbrown 0.14.5", 795 | ] 796 | 797 | [[package]] 798 | name = "heck" 799 | version = "0.5.0" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 802 | 803 | [[package]] 804 | name = "hermit-abi" 805 | version = "0.3.9" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 808 | 809 | [[package]] 810 | name = "hex" 811 | version = "0.4.3" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 814 | 815 | [[package]] 816 | name = "html-escape" 817 | version = "0.2.13" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" 820 | dependencies = [ 821 | "utf8-width", 822 | ] 823 | 824 | [[package]] 825 | name = "html_parser" 826 | version = "0.7.0" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "f6f56db07b6612644f6f7719f8ef944f75fff9d6378fdf3d316fd32194184abd" 829 | dependencies = [ 830 | "doc-comment", 831 | "pest", 832 | "pest_derive", 833 | "serde", 834 | "serde_derive", 835 | "serde_json", 836 | "thiserror", 837 | ] 838 | 839 | [[package]] 840 | name = "http" 841 | version = "0.2.12" 842 | source = "registry+https://github.com/rust-lang/crates.io-index" 843 | checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" 844 | dependencies = [ 845 | "bytes", 846 | "fnv", 847 | "itoa", 848 | ] 849 | 850 | [[package]] 851 | name = "http-body" 852 | version = "0.4.6" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 855 | dependencies = [ 856 | "bytes", 857 | "http", 858 | "pin-project-lite", 859 | ] 860 | 861 | [[package]] 862 | name = "httparse" 863 | version = "1.9.5" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" 866 | 867 | [[package]] 868 | name = "httpdate" 869 | version = "1.0.3" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 872 | 873 | [[package]] 874 | name = "hyper" 875 | version = "0.14.31" 876 | source = "registry+https://github.com/rust-lang/crates.io-index" 877 | checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" 878 | dependencies = [ 879 | "bytes", 880 | "futures-channel", 881 | "futures-core", 882 | "futures-util", 883 | "h2", 884 | "http", 885 | "http-body", 886 | "httparse", 887 | "httpdate", 888 | "itoa", 889 | "pin-project-lite", 890 | "socket2", 891 | "tokio", 892 | "tower-service", 893 | "tracing", 894 | "want", 895 | ] 896 | 897 | [[package]] 898 | name = "hyper-tls" 899 | version = "0.5.0" 900 | source = "registry+https://github.com/rust-lang/crates.io-index" 901 | checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" 902 | dependencies = [ 903 | "bytes", 904 | "hyper", 905 | "native-tls", 906 | "tokio", 907 | "tokio-native-tls", 908 | ] 909 | 910 | [[package]] 911 | name = "iana-time-zone" 912 | version = "0.1.61" 913 | source = "registry+https://github.com/rust-lang/crates.io-index" 914 | checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" 915 | dependencies = [ 916 | "android_system_properties", 917 | "core-foundation-sys", 918 | "iana-time-zone-haiku", 919 | "js-sys", 920 | "wasm-bindgen", 921 | "windows-core", 922 | ] 923 | 924 | [[package]] 925 | name = "iana-time-zone-haiku" 926 | version = "0.1.2" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 929 | dependencies = [ 930 | "cc", 931 | ] 932 | 933 | [[package]] 934 | name = "ident_case" 935 | version = "1.0.1" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 938 | 939 | [[package]] 940 | name = "idna" 941 | version = "0.5.0" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 944 | dependencies = [ 945 | "unicode-bidi", 946 | "unicode-normalization", 947 | ] 948 | 949 | [[package]] 950 | name = "indexmap" 951 | version = "1.9.3" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 954 | dependencies = [ 955 | "autocfg", 956 | "hashbrown 0.12.3", 957 | "serde", 958 | ] 959 | 960 | [[package]] 961 | name = "indexmap" 962 | version = "2.6.0" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 965 | dependencies = [ 966 | "equivalent", 967 | "hashbrown 0.15.0", 968 | ] 969 | 970 | [[package]] 971 | name = "indoc" 972 | version = "2.0.5" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" 975 | 976 | [[package]] 977 | name = "ipnet" 978 | version = "2.10.1" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" 981 | 982 | [[package]] 983 | name = "is_terminal_polyfill" 984 | version = "1.70.1" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 987 | 988 | [[package]] 989 | name = "itoa" 990 | version = "1.0.11" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 993 | 994 | [[package]] 995 | name = "js-sys" 996 | version = "0.3.72" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" 999 | dependencies = [ 1000 | "wasm-bindgen", 1001 | ] 1002 | 1003 | [[package]] 1004 | name = "libc" 1005 | version = "0.2.161" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" 1008 | 1009 | [[package]] 1010 | name = "libredox" 1011 | version = "0.1.3" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 1014 | dependencies = [ 1015 | "bitflags 2.6.0", 1016 | "libc", 1017 | ] 1018 | 1019 | [[package]] 1020 | name = "libsqlite3-sys" 1021 | version = "0.26.0" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" 1024 | dependencies = [ 1025 | "cc", 1026 | "pkg-config", 1027 | "vcpkg", 1028 | ] 1029 | 1030 | [[package]] 1031 | name = "linux-raw-sys" 1032 | version = "0.4.14" 1033 | source = "registry+https://github.com/rust-lang/crates.io-index" 1034 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 1035 | 1036 | [[package]] 1037 | name = "lock_api" 1038 | version = "0.4.12" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 1041 | dependencies = [ 1042 | "autocfg", 1043 | "scopeguard", 1044 | ] 1045 | 1046 | [[package]] 1047 | name = "log" 1048 | version = "0.4.22" 1049 | source = "registry+https://github.com/rust-lang/crates.io-index" 1050 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 1051 | 1052 | [[package]] 1053 | name = "lz4_flex" 1054 | version = "0.10.0" 1055 | source = "registry+https://github.com/rust-lang/crates.io-index" 1056 | checksum = "8b8c72594ac26bfd34f2d99dfced2edfaddfe8a476e3ff2ca0eb293d925c4f83" 1057 | dependencies = [ 1058 | "twox-hash", 1059 | ] 1060 | 1061 | [[package]] 1062 | name = "memchr" 1063 | version = "2.7.4" 1064 | source = "registry+https://github.com/rust-lang/crates.io-index" 1065 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 1066 | 1067 | [[package]] 1068 | name = "memmap2" 1069 | version = "0.5.10" 1070 | source = "registry+https://github.com/rust-lang/crates.io-index" 1071 | checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" 1072 | dependencies = [ 1073 | "libc", 1074 | ] 1075 | 1076 | [[package]] 1077 | name = "mime" 1078 | version = "0.3.17" 1079 | source = "registry+https://github.com/rust-lang/crates.io-index" 1080 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1081 | 1082 | [[package]] 1083 | name = "miniz_oxide" 1084 | version = "0.8.0" 1085 | source = "registry+https://github.com/rust-lang/crates.io-index" 1086 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 1087 | dependencies = [ 1088 | "adler2", 1089 | ] 1090 | 1091 | [[package]] 1092 | name = "mio" 1093 | version = "0.8.11" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 1096 | dependencies = [ 1097 | "libc", 1098 | "log", 1099 | "wasi", 1100 | "windows-sys 0.48.0", 1101 | ] 1102 | 1103 | [[package]] 1104 | name = "mio" 1105 | version = "1.0.2" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 1108 | dependencies = [ 1109 | "hermit-abi", 1110 | "libc", 1111 | "wasi", 1112 | "windows-sys 0.52.0", 1113 | ] 1114 | 1115 | [[package]] 1116 | name = "moccasin" 1117 | version = "0.1.2" 1118 | dependencies = [ 1119 | "anyhow", 1120 | "chrono", 1121 | "clap", 1122 | "colorsys", 1123 | "crossterm 0.27.0", 1124 | "directories", 1125 | "futures", 1126 | "html-escape", 1127 | "html_parser", 1128 | "log", 1129 | "polodb_core", 1130 | "ratatui", 1131 | "reqwest", 1132 | "rss", 1133 | "rusqlite", 1134 | "serde", 1135 | "simplelog", 1136 | "tokio", 1137 | "toml", 1138 | "toml_edit", 1139 | ] 1140 | 1141 | [[package]] 1142 | name = "native-tls" 1143 | version = "0.2.12" 1144 | source = "registry+https://github.com/rust-lang/crates.io-index" 1145 | checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" 1146 | dependencies = [ 1147 | "libc", 1148 | "log", 1149 | "openssl", 1150 | "openssl-probe", 1151 | "openssl-sys", 1152 | "schannel", 1153 | "security-framework", 1154 | "security-framework-sys", 1155 | "tempfile", 1156 | ] 1157 | 1158 | [[package]] 1159 | name = "never" 1160 | version = "0.1.0" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91" 1163 | 1164 | [[package]] 1165 | name = "num-conv" 1166 | version = "0.1.0" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1169 | 1170 | [[package]] 1171 | name = "num-traits" 1172 | version = "0.2.19" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1175 | dependencies = [ 1176 | "autocfg", 1177 | ] 1178 | 1179 | [[package]] 1180 | name = "num_enum" 1181 | version = "0.6.1" 1182 | source = "registry+https://github.com/rust-lang/crates.io-index" 1183 | checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" 1184 | dependencies = [ 1185 | "num_enum_derive", 1186 | ] 1187 | 1188 | [[package]] 1189 | name = "num_enum_derive" 1190 | version = "0.6.1" 1191 | source = "registry+https://github.com/rust-lang/crates.io-index" 1192 | checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" 1193 | dependencies = [ 1194 | "proc-macro-crate", 1195 | "proc-macro2", 1196 | "quote", 1197 | "syn", 1198 | ] 1199 | 1200 | [[package]] 1201 | name = "num_threads" 1202 | version = "0.1.7" 1203 | source = "registry+https://github.com/rust-lang/crates.io-index" 1204 | checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" 1205 | dependencies = [ 1206 | "libc", 1207 | ] 1208 | 1209 | [[package]] 1210 | name = "object" 1211 | version = "0.36.5" 1212 | source = "registry+https://github.com/rust-lang/crates.io-index" 1213 | checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" 1214 | dependencies = [ 1215 | "memchr", 1216 | ] 1217 | 1218 | [[package]] 1219 | name = "once_cell" 1220 | version = "1.20.2" 1221 | source = "registry+https://github.com/rust-lang/crates.io-index" 1222 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 1223 | 1224 | [[package]] 1225 | name = "openssl" 1226 | version = "0.10.68" 1227 | source = "registry+https://github.com/rust-lang/crates.io-index" 1228 | checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" 1229 | dependencies = [ 1230 | "bitflags 2.6.0", 1231 | "cfg-if", 1232 | "foreign-types", 1233 | "libc", 1234 | "once_cell", 1235 | "openssl-macros", 1236 | "openssl-sys", 1237 | ] 1238 | 1239 | [[package]] 1240 | name = "openssl-macros" 1241 | version = "0.1.1" 1242 | source = "registry+https://github.com/rust-lang/crates.io-index" 1243 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 1244 | dependencies = [ 1245 | "proc-macro2", 1246 | "quote", 1247 | "syn", 1248 | ] 1249 | 1250 | [[package]] 1251 | name = "openssl-probe" 1252 | version = "0.1.5" 1253 | source = "registry+https://github.com/rust-lang/crates.io-index" 1254 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 1255 | 1256 | [[package]] 1257 | name = "openssl-sys" 1258 | version = "0.9.104" 1259 | source = "registry+https://github.com/rust-lang/crates.io-index" 1260 | checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" 1261 | dependencies = [ 1262 | "cc", 1263 | "libc", 1264 | "pkg-config", 1265 | "vcpkg", 1266 | ] 1267 | 1268 | [[package]] 1269 | name = "option-ext" 1270 | version = "0.2.0" 1271 | source = "registry+https://github.com/rust-lang/crates.io-index" 1272 | checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 1273 | 1274 | [[package]] 1275 | name = "parking_lot" 1276 | version = "0.12.3" 1277 | source = "registry+https://github.com/rust-lang/crates.io-index" 1278 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1279 | dependencies = [ 1280 | "lock_api", 1281 | "parking_lot_core", 1282 | ] 1283 | 1284 | [[package]] 1285 | name = "parking_lot_core" 1286 | version = "0.9.10" 1287 | source = "registry+https://github.com/rust-lang/crates.io-index" 1288 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1289 | dependencies = [ 1290 | "cfg-if", 1291 | "libc", 1292 | "redox_syscall", 1293 | "smallvec", 1294 | "windows-targets 0.52.6", 1295 | ] 1296 | 1297 | [[package]] 1298 | name = "paste" 1299 | version = "1.0.15" 1300 | source = "registry+https://github.com/rust-lang/crates.io-index" 1301 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 1302 | 1303 | [[package]] 1304 | name = "percent-encoding" 1305 | version = "2.3.1" 1306 | source = "registry+https://github.com/rust-lang/crates.io-index" 1307 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1308 | 1309 | [[package]] 1310 | name = "pest" 1311 | version = "2.7.14" 1312 | source = "registry+https://github.com/rust-lang/crates.io-index" 1313 | checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" 1314 | dependencies = [ 1315 | "memchr", 1316 | "thiserror", 1317 | "ucd-trie", 1318 | ] 1319 | 1320 | [[package]] 1321 | name = "pest_derive" 1322 | version = "2.7.14" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" 1325 | dependencies = [ 1326 | "pest", 1327 | "pest_generator", 1328 | ] 1329 | 1330 | [[package]] 1331 | name = "pest_generator" 1332 | version = "2.7.14" 1333 | source = "registry+https://github.com/rust-lang/crates.io-index" 1334 | checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" 1335 | dependencies = [ 1336 | "pest", 1337 | "pest_meta", 1338 | "proc-macro2", 1339 | "quote", 1340 | "syn", 1341 | ] 1342 | 1343 | [[package]] 1344 | name = "pest_meta" 1345 | version = "2.7.14" 1346 | source = "registry+https://github.com/rust-lang/crates.io-index" 1347 | checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" 1348 | dependencies = [ 1349 | "once_cell", 1350 | "pest", 1351 | "sha2", 1352 | ] 1353 | 1354 | [[package]] 1355 | name = "pin-project-lite" 1356 | version = "0.2.14" 1357 | source = "registry+https://github.com/rust-lang/crates.io-index" 1358 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 1359 | 1360 | [[package]] 1361 | name = "pin-utils" 1362 | version = "0.1.0" 1363 | source = "registry+https://github.com/rust-lang/crates.io-index" 1364 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1365 | 1366 | [[package]] 1367 | name = "pkg-config" 1368 | version = "0.3.31" 1369 | source = "registry+https://github.com/rust-lang/crates.io-index" 1370 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 1371 | 1372 | [[package]] 1373 | name = "polodb_core" 1374 | version = "4.4.2" 1375 | source = "registry+https://github.com/rust-lang/crates.io-index" 1376 | checksum = "714bc48cfa44e6713512efabffe69887e564e5d88b0fdc83d27c0c1d3137e857" 1377 | dependencies = [ 1378 | "bson", 1379 | "byteorder", 1380 | "crc64fast", 1381 | "getrandom", 1382 | "hashbrown 0.13.2", 1383 | "indexmap 1.9.3", 1384 | "js-sys", 1385 | "libc", 1386 | "lz4_flex", 1387 | "memmap2", 1388 | "num_enum", 1389 | "regex", 1390 | "serde", 1391 | "serde-wasm-bindgen", 1392 | "smallvec", 1393 | "thiserror", 1394 | "uuid", 1395 | "wasm-bindgen", 1396 | "wasm-bindgen-futures", 1397 | "web-sys", 1398 | "winapi", 1399 | ] 1400 | 1401 | [[package]] 1402 | name = "powerfmt" 1403 | version = "0.2.0" 1404 | source = "registry+https://github.com/rust-lang/crates.io-index" 1405 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1406 | 1407 | [[package]] 1408 | name = "ppv-lite86" 1409 | version = "0.2.20" 1410 | source = "registry+https://github.com/rust-lang/crates.io-index" 1411 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 1412 | dependencies = [ 1413 | "zerocopy", 1414 | ] 1415 | 1416 | [[package]] 1417 | name = "proc-macro-crate" 1418 | version = "1.3.1" 1419 | source = "registry+https://github.com/rust-lang/crates.io-index" 1420 | checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" 1421 | dependencies = [ 1422 | "once_cell", 1423 | "toml_edit", 1424 | ] 1425 | 1426 | [[package]] 1427 | name = "proc-macro2" 1428 | version = "1.0.88" 1429 | source = "registry+https://github.com/rust-lang/crates.io-index" 1430 | checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" 1431 | dependencies = [ 1432 | "unicode-ident", 1433 | ] 1434 | 1435 | [[package]] 1436 | name = "quick-xml" 1437 | version = "0.36.2" 1438 | source = "registry+https://github.com/rust-lang/crates.io-index" 1439 | checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" 1440 | dependencies = [ 1441 | "encoding_rs", 1442 | "memchr", 1443 | ] 1444 | 1445 | [[package]] 1446 | name = "quote" 1447 | version = "1.0.37" 1448 | source = "registry+https://github.com/rust-lang/crates.io-index" 1449 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 1450 | dependencies = [ 1451 | "proc-macro2", 1452 | ] 1453 | 1454 | [[package]] 1455 | name = "radium" 1456 | version = "0.7.0" 1457 | source = "registry+https://github.com/rust-lang/crates.io-index" 1458 | checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" 1459 | 1460 | [[package]] 1461 | name = "rand" 1462 | version = "0.8.5" 1463 | source = "registry+https://github.com/rust-lang/crates.io-index" 1464 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1465 | dependencies = [ 1466 | "libc", 1467 | "rand_chacha", 1468 | "rand_core", 1469 | ] 1470 | 1471 | [[package]] 1472 | name = "rand_chacha" 1473 | version = "0.3.1" 1474 | source = "registry+https://github.com/rust-lang/crates.io-index" 1475 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1476 | dependencies = [ 1477 | "ppv-lite86", 1478 | "rand_core", 1479 | ] 1480 | 1481 | [[package]] 1482 | name = "rand_core" 1483 | version = "0.6.4" 1484 | source = "registry+https://github.com/rust-lang/crates.io-index" 1485 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1486 | dependencies = [ 1487 | "getrandom", 1488 | ] 1489 | 1490 | [[package]] 1491 | name = "ratatui" 1492 | version = "0.22.0" 1493 | source = "registry+https://github.com/rust-lang/crates.io-index" 1494 | checksum = "8285baa38bdc9f879d92c0e37cb562ef38aa3aeefca22b3200186bc39242d3d5" 1495 | dependencies = [ 1496 | "bitflags 2.6.0", 1497 | "cassowary", 1498 | "crossterm 0.26.1", 1499 | "indoc", 1500 | "paste", 1501 | "time", 1502 | "unicode-segmentation", 1503 | "unicode-width", 1504 | ] 1505 | 1506 | [[package]] 1507 | name = "redox_syscall" 1508 | version = "0.5.7" 1509 | source = "registry+https://github.com/rust-lang/crates.io-index" 1510 | checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" 1511 | dependencies = [ 1512 | "bitflags 2.6.0", 1513 | ] 1514 | 1515 | [[package]] 1516 | name = "redox_users" 1517 | version = "0.4.6" 1518 | source = "registry+https://github.com/rust-lang/crates.io-index" 1519 | checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" 1520 | dependencies = [ 1521 | "getrandom", 1522 | "libredox", 1523 | "thiserror", 1524 | ] 1525 | 1526 | [[package]] 1527 | name = "regex" 1528 | version = "1.11.0" 1529 | source = "registry+https://github.com/rust-lang/crates.io-index" 1530 | checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" 1531 | dependencies = [ 1532 | "aho-corasick", 1533 | "memchr", 1534 | "regex-automata", 1535 | "regex-syntax", 1536 | ] 1537 | 1538 | [[package]] 1539 | name = "regex-automata" 1540 | version = "0.4.8" 1541 | source = "registry+https://github.com/rust-lang/crates.io-index" 1542 | checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" 1543 | dependencies = [ 1544 | "aho-corasick", 1545 | "memchr", 1546 | "regex-syntax", 1547 | ] 1548 | 1549 | [[package]] 1550 | name = "regex-syntax" 1551 | version = "0.8.5" 1552 | source = "registry+https://github.com/rust-lang/crates.io-index" 1553 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1554 | 1555 | [[package]] 1556 | name = "reqwest" 1557 | version = "0.11.27" 1558 | source = "registry+https://github.com/rust-lang/crates.io-index" 1559 | checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" 1560 | dependencies = [ 1561 | "base64 0.21.7", 1562 | "bytes", 1563 | "encoding_rs", 1564 | "futures-core", 1565 | "futures-util", 1566 | "h2", 1567 | "http", 1568 | "http-body", 1569 | "hyper", 1570 | "hyper-tls", 1571 | "ipnet", 1572 | "js-sys", 1573 | "log", 1574 | "mime", 1575 | "native-tls", 1576 | "once_cell", 1577 | "percent-encoding", 1578 | "pin-project-lite", 1579 | "rustls-pemfile", 1580 | "serde", 1581 | "serde_json", 1582 | "serde_urlencoded", 1583 | "sync_wrapper", 1584 | "system-configuration", 1585 | "tokio", 1586 | "tokio-native-tls", 1587 | "tower-service", 1588 | "url", 1589 | "wasm-bindgen", 1590 | "wasm-bindgen-futures", 1591 | "web-sys", 1592 | "winreg", 1593 | ] 1594 | 1595 | [[package]] 1596 | name = "rss" 1597 | version = "2.0.9" 1598 | source = "registry+https://github.com/rust-lang/crates.io-index" 1599 | checksum = "27e92048f840d98c6d6dd870af9101610ea9ff413f11f1bcebf4f4c31d96d957" 1600 | dependencies = [ 1601 | "atom_syndication", 1602 | "derive_builder", 1603 | "never", 1604 | "quick-xml", 1605 | ] 1606 | 1607 | [[package]] 1608 | name = "rusqlite" 1609 | version = "0.29.0" 1610 | source = "registry+https://github.com/rust-lang/crates.io-index" 1611 | checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2" 1612 | dependencies = [ 1613 | "bitflags 2.6.0", 1614 | "fallible-iterator", 1615 | "fallible-streaming-iterator", 1616 | "hashlink", 1617 | "libsqlite3-sys", 1618 | "serde_json", 1619 | "smallvec", 1620 | ] 1621 | 1622 | [[package]] 1623 | name = "rustc-demangle" 1624 | version = "0.1.24" 1625 | source = "registry+https://github.com/rust-lang/crates.io-index" 1626 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1627 | 1628 | [[package]] 1629 | name = "rustix" 1630 | version = "0.38.37" 1631 | source = "registry+https://github.com/rust-lang/crates.io-index" 1632 | checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" 1633 | dependencies = [ 1634 | "bitflags 2.6.0", 1635 | "errno", 1636 | "libc", 1637 | "linux-raw-sys", 1638 | "windows-sys 0.52.0", 1639 | ] 1640 | 1641 | [[package]] 1642 | name = "rustls-pemfile" 1643 | version = "1.0.4" 1644 | source = "registry+https://github.com/rust-lang/crates.io-index" 1645 | checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" 1646 | dependencies = [ 1647 | "base64 0.21.7", 1648 | ] 1649 | 1650 | [[package]] 1651 | name = "ryu" 1652 | version = "1.0.18" 1653 | source = "registry+https://github.com/rust-lang/crates.io-index" 1654 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 1655 | 1656 | [[package]] 1657 | name = "schannel" 1658 | version = "0.1.26" 1659 | source = "registry+https://github.com/rust-lang/crates.io-index" 1660 | checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" 1661 | dependencies = [ 1662 | "windows-sys 0.59.0", 1663 | ] 1664 | 1665 | [[package]] 1666 | name = "scopeguard" 1667 | version = "1.2.0" 1668 | source = "registry+https://github.com/rust-lang/crates.io-index" 1669 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1670 | 1671 | [[package]] 1672 | name = "security-framework" 1673 | version = "2.11.1" 1674 | source = "registry+https://github.com/rust-lang/crates.io-index" 1675 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 1676 | dependencies = [ 1677 | "bitflags 2.6.0", 1678 | "core-foundation", 1679 | "core-foundation-sys", 1680 | "libc", 1681 | "security-framework-sys", 1682 | ] 1683 | 1684 | [[package]] 1685 | name = "security-framework-sys" 1686 | version = "2.12.0" 1687 | source = "registry+https://github.com/rust-lang/crates.io-index" 1688 | checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" 1689 | dependencies = [ 1690 | "core-foundation-sys", 1691 | "libc", 1692 | ] 1693 | 1694 | [[package]] 1695 | name = "serde" 1696 | version = "1.0.210" 1697 | source = "registry+https://github.com/rust-lang/crates.io-index" 1698 | checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" 1699 | dependencies = [ 1700 | "serde_derive", 1701 | ] 1702 | 1703 | [[package]] 1704 | name = "serde-wasm-bindgen" 1705 | version = "0.5.0" 1706 | source = "registry+https://github.com/rust-lang/crates.io-index" 1707 | checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" 1708 | dependencies = [ 1709 | "js-sys", 1710 | "serde", 1711 | "wasm-bindgen", 1712 | ] 1713 | 1714 | [[package]] 1715 | name = "serde_bytes" 1716 | version = "0.11.15" 1717 | source = "registry+https://github.com/rust-lang/crates.io-index" 1718 | checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" 1719 | dependencies = [ 1720 | "serde", 1721 | ] 1722 | 1723 | [[package]] 1724 | name = "serde_derive" 1725 | version = "1.0.210" 1726 | source = "registry+https://github.com/rust-lang/crates.io-index" 1727 | checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" 1728 | dependencies = [ 1729 | "proc-macro2", 1730 | "quote", 1731 | "syn", 1732 | ] 1733 | 1734 | [[package]] 1735 | name = "serde_json" 1736 | version = "1.0.129" 1737 | source = "registry+https://github.com/rust-lang/crates.io-index" 1738 | checksum = "6dbcf9b78a125ee667ae19388837dd12294b858d101fdd393cb9d5501ef09eb2" 1739 | dependencies = [ 1740 | "indexmap 2.6.0", 1741 | "itoa", 1742 | "memchr", 1743 | "ryu", 1744 | "serde", 1745 | ] 1746 | 1747 | [[package]] 1748 | name = "serde_spanned" 1749 | version = "0.6.8" 1750 | source = "registry+https://github.com/rust-lang/crates.io-index" 1751 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 1752 | dependencies = [ 1753 | "serde", 1754 | ] 1755 | 1756 | [[package]] 1757 | name = "serde_urlencoded" 1758 | version = "0.7.1" 1759 | source = "registry+https://github.com/rust-lang/crates.io-index" 1760 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1761 | dependencies = [ 1762 | "form_urlencoded", 1763 | "itoa", 1764 | "ryu", 1765 | "serde", 1766 | ] 1767 | 1768 | [[package]] 1769 | name = "sha2" 1770 | version = "0.10.8" 1771 | source = "registry+https://github.com/rust-lang/crates.io-index" 1772 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 1773 | dependencies = [ 1774 | "cfg-if", 1775 | "cpufeatures", 1776 | "digest", 1777 | ] 1778 | 1779 | [[package]] 1780 | name = "shlex" 1781 | version = "1.3.0" 1782 | source = "registry+https://github.com/rust-lang/crates.io-index" 1783 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1784 | 1785 | [[package]] 1786 | name = "signal-hook" 1787 | version = "0.3.17" 1788 | source = "registry+https://github.com/rust-lang/crates.io-index" 1789 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 1790 | dependencies = [ 1791 | "libc", 1792 | "signal-hook-registry", 1793 | ] 1794 | 1795 | [[package]] 1796 | name = "signal-hook-mio" 1797 | version = "0.2.4" 1798 | source = "registry+https://github.com/rust-lang/crates.io-index" 1799 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 1800 | dependencies = [ 1801 | "libc", 1802 | "mio 0.8.11", 1803 | "signal-hook", 1804 | ] 1805 | 1806 | [[package]] 1807 | name = "signal-hook-registry" 1808 | version = "1.4.2" 1809 | source = "registry+https://github.com/rust-lang/crates.io-index" 1810 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1811 | dependencies = [ 1812 | "libc", 1813 | ] 1814 | 1815 | [[package]] 1816 | name = "simplelog" 1817 | version = "0.12.2" 1818 | source = "registry+https://github.com/rust-lang/crates.io-index" 1819 | checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0" 1820 | dependencies = [ 1821 | "log", 1822 | "termcolor", 1823 | "time", 1824 | ] 1825 | 1826 | [[package]] 1827 | name = "slab" 1828 | version = "0.4.9" 1829 | source = "registry+https://github.com/rust-lang/crates.io-index" 1830 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1831 | dependencies = [ 1832 | "autocfg", 1833 | ] 1834 | 1835 | [[package]] 1836 | name = "smallvec" 1837 | version = "1.13.2" 1838 | source = "registry+https://github.com/rust-lang/crates.io-index" 1839 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1840 | 1841 | [[package]] 1842 | name = "socket2" 1843 | version = "0.5.7" 1844 | source = "registry+https://github.com/rust-lang/crates.io-index" 1845 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 1846 | dependencies = [ 1847 | "libc", 1848 | "windows-sys 0.52.0", 1849 | ] 1850 | 1851 | [[package]] 1852 | name = "static_assertions" 1853 | version = "1.1.0" 1854 | source = "registry+https://github.com/rust-lang/crates.io-index" 1855 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1856 | 1857 | [[package]] 1858 | name = "strsim" 1859 | version = "0.11.1" 1860 | source = "registry+https://github.com/rust-lang/crates.io-index" 1861 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1862 | 1863 | [[package]] 1864 | name = "syn" 1865 | version = "2.0.79" 1866 | source = "registry+https://github.com/rust-lang/crates.io-index" 1867 | checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" 1868 | dependencies = [ 1869 | "proc-macro2", 1870 | "quote", 1871 | "unicode-ident", 1872 | ] 1873 | 1874 | [[package]] 1875 | name = "sync_wrapper" 1876 | version = "0.1.2" 1877 | source = "registry+https://github.com/rust-lang/crates.io-index" 1878 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 1879 | 1880 | [[package]] 1881 | name = "system-configuration" 1882 | version = "0.5.1" 1883 | source = "registry+https://github.com/rust-lang/crates.io-index" 1884 | checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" 1885 | dependencies = [ 1886 | "bitflags 1.3.2", 1887 | "core-foundation", 1888 | "system-configuration-sys", 1889 | ] 1890 | 1891 | [[package]] 1892 | name = "system-configuration-sys" 1893 | version = "0.5.0" 1894 | source = "registry+https://github.com/rust-lang/crates.io-index" 1895 | checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" 1896 | dependencies = [ 1897 | "core-foundation-sys", 1898 | "libc", 1899 | ] 1900 | 1901 | [[package]] 1902 | name = "tap" 1903 | version = "1.0.1" 1904 | source = "registry+https://github.com/rust-lang/crates.io-index" 1905 | checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" 1906 | 1907 | [[package]] 1908 | name = "tempfile" 1909 | version = "3.13.0" 1910 | source = "registry+https://github.com/rust-lang/crates.io-index" 1911 | checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" 1912 | dependencies = [ 1913 | "cfg-if", 1914 | "fastrand", 1915 | "once_cell", 1916 | "rustix", 1917 | "windows-sys 0.59.0", 1918 | ] 1919 | 1920 | [[package]] 1921 | name = "termcolor" 1922 | version = "1.4.1" 1923 | source = "registry+https://github.com/rust-lang/crates.io-index" 1924 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 1925 | dependencies = [ 1926 | "winapi-util", 1927 | ] 1928 | 1929 | [[package]] 1930 | name = "thiserror" 1931 | version = "1.0.64" 1932 | source = "registry+https://github.com/rust-lang/crates.io-index" 1933 | checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" 1934 | dependencies = [ 1935 | "thiserror-impl", 1936 | ] 1937 | 1938 | [[package]] 1939 | name = "thiserror-impl" 1940 | version = "1.0.64" 1941 | source = "registry+https://github.com/rust-lang/crates.io-index" 1942 | checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" 1943 | dependencies = [ 1944 | "proc-macro2", 1945 | "quote", 1946 | "syn", 1947 | ] 1948 | 1949 | [[package]] 1950 | name = "time" 1951 | version = "0.3.36" 1952 | source = "registry+https://github.com/rust-lang/crates.io-index" 1953 | checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" 1954 | dependencies = [ 1955 | "deranged", 1956 | "itoa", 1957 | "libc", 1958 | "num-conv", 1959 | "num_threads", 1960 | "powerfmt", 1961 | "serde", 1962 | "time-core", 1963 | "time-macros", 1964 | ] 1965 | 1966 | [[package]] 1967 | name = "time-core" 1968 | version = "0.1.2" 1969 | source = "registry+https://github.com/rust-lang/crates.io-index" 1970 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 1971 | 1972 | [[package]] 1973 | name = "time-macros" 1974 | version = "0.2.18" 1975 | source = "registry+https://github.com/rust-lang/crates.io-index" 1976 | checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" 1977 | dependencies = [ 1978 | "num-conv", 1979 | "time-core", 1980 | ] 1981 | 1982 | [[package]] 1983 | name = "tinyvec" 1984 | version = "1.8.0" 1985 | source = "registry+https://github.com/rust-lang/crates.io-index" 1986 | checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" 1987 | dependencies = [ 1988 | "tinyvec_macros", 1989 | ] 1990 | 1991 | [[package]] 1992 | name = "tinyvec_macros" 1993 | version = "0.1.1" 1994 | source = "registry+https://github.com/rust-lang/crates.io-index" 1995 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1996 | 1997 | [[package]] 1998 | name = "tokio" 1999 | version = "1.40.0" 2000 | source = "registry+https://github.com/rust-lang/crates.io-index" 2001 | checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" 2002 | dependencies = [ 2003 | "backtrace", 2004 | "bytes", 2005 | "libc", 2006 | "mio 1.0.2", 2007 | "parking_lot", 2008 | "pin-project-lite", 2009 | "signal-hook-registry", 2010 | "socket2", 2011 | "tokio-macros", 2012 | "windows-sys 0.52.0", 2013 | ] 2014 | 2015 | [[package]] 2016 | name = "tokio-macros" 2017 | version = "2.4.0" 2018 | source = "registry+https://github.com/rust-lang/crates.io-index" 2019 | checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" 2020 | dependencies = [ 2021 | "proc-macro2", 2022 | "quote", 2023 | "syn", 2024 | ] 2025 | 2026 | [[package]] 2027 | name = "tokio-native-tls" 2028 | version = "0.3.1" 2029 | source = "registry+https://github.com/rust-lang/crates.io-index" 2030 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 2031 | dependencies = [ 2032 | "native-tls", 2033 | "tokio", 2034 | ] 2035 | 2036 | [[package]] 2037 | name = "tokio-util" 2038 | version = "0.7.12" 2039 | source = "registry+https://github.com/rust-lang/crates.io-index" 2040 | checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" 2041 | dependencies = [ 2042 | "bytes", 2043 | "futures-core", 2044 | "futures-sink", 2045 | "pin-project-lite", 2046 | "tokio", 2047 | ] 2048 | 2049 | [[package]] 2050 | name = "toml" 2051 | version = "0.7.8" 2052 | source = "registry+https://github.com/rust-lang/crates.io-index" 2053 | checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" 2054 | dependencies = [ 2055 | "serde", 2056 | "serde_spanned", 2057 | "toml_datetime", 2058 | "toml_edit", 2059 | ] 2060 | 2061 | [[package]] 2062 | name = "toml_datetime" 2063 | version = "0.6.8" 2064 | source = "registry+https://github.com/rust-lang/crates.io-index" 2065 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 2066 | dependencies = [ 2067 | "serde", 2068 | ] 2069 | 2070 | [[package]] 2071 | name = "toml_edit" 2072 | version = "0.19.15" 2073 | source = "registry+https://github.com/rust-lang/crates.io-index" 2074 | checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" 2075 | dependencies = [ 2076 | "indexmap 2.6.0", 2077 | "serde", 2078 | "serde_spanned", 2079 | "toml_datetime", 2080 | "winnow", 2081 | ] 2082 | 2083 | [[package]] 2084 | name = "tower-service" 2085 | version = "0.3.3" 2086 | source = "registry+https://github.com/rust-lang/crates.io-index" 2087 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 2088 | 2089 | [[package]] 2090 | name = "tracing" 2091 | version = "0.1.40" 2092 | source = "registry+https://github.com/rust-lang/crates.io-index" 2093 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 2094 | dependencies = [ 2095 | "pin-project-lite", 2096 | "tracing-core", 2097 | ] 2098 | 2099 | [[package]] 2100 | name = "tracing-core" 2101 | version = "0.1.32" 2102 | source = "registry+https://github.com/rust-lang/crates.io-index" 2103 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 2104 | dependencies = [ 2105 | "once_cell", 2106 | ] 2107 | 2108 | [[package]] 2109 | name = "try-lock" 2110 | version = "0.2.5" 2111 | source = "registry+https://github.com/rust-lang/crates.io-index" 2112 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 2113 | 2114 | [[package]] 2115 | name = "twox-hash" 2116 | version = "1.6.3" 2117 | source = "registry+https://github.com/rust-lang/crates.io-index" 2118 | checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" 2119 | dependencies = [ 2120 | "cfg-if", 2121 | "static_assertions", 2122 | ] 2123 | 2124 | [[package]] 2125 | name = "typenum" 2126 | version = "1.17.0" 2127 | source = "registry+https://github.com/rust-lang/crates.io-index" 2128 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 2129 | 2130 | [[package]] 2131 | name = "ucd-trie" 2132 | version = "0.1.7" 2133 | source = "registry+https://github.com/rust-lang/crates.io-index" 2134 | checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" 2135 | 2136 | [[package]] 2137 | name = "unicode-bidi" 2138 | version = "0.3.17" 2139 | source = "registry+https://github.com/rust-lang/crates.io-index" 2140 | checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" 2141 | 2142 | [[package]] 2143 | name = "unicode-ident" 2144 | version = "1.0.13" 2145 | source = "registry+https://github.com/rust-lang/crates.io-index" 2146 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 2147 | 2148 | [[package]] 2149 | name = "unicode-normalization" 2150 | version = "0.1.24" 2151 | source = "registry+https://github.com/rust-lang/crates.io-index" 2152 | checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" 2153 | dependencies = [ 2154 | "tinyvec", 2155 | ] 2156 | 2157 | [[package]] 2158 | name = "unicode-segmentation" 2159 | version = "1.12.0" 2160 | source = "registry+https://github.com/rust-lang/crates.io-index" 2161 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 2162 | 2163 | [[package]] 2164 | name = "unicode-width" 2165 | version = "0.1.14" 2166 | source = "registry+https://github.com/rust-lang/crates.io-index" 2167 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 2168 | 2169 | [[package]] 2170 | name = "url" 2171 | version = "2.5.2" 2172 | source = "registry+https://github.com/rust-lang/crates.io-index" 2173 | checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" 2174 | dependencies = [ 2175 | "form_urlencoded", 2176 | "idna", 2177 | "percent-encoding", 2178 | ] 2179 | 2180 | [[package]] 2181 | name = "utf8-width" 2182 | version = "0.1.7" 2183 | source = "registry+https://github.com/rust-lang/crates.io-index" 2184 | checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" 2185 | 2186 | [[package]] 2187 | name = "utf8parse" 2188 | version = "0.2.2" 2189 | source = "registry+https://github.com/rust-lang/crates.io-index" 2190 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 2191 | 2192 | [[package]] 2193 | name = "uuid" 2194 | version = "1.11.0" 2195 | source = "registry+https://github.com/rust-lang/crates.io-index" 2196 | checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" 2197 | dependencies = [ 2198 | "atomic", 2199 | "getrandom", 2200 | "serde", 2201 | "wasm-bindgen", 2202 | ] 2203 | 2204 | [[package]] 2205 | name = "vcpkg" 2206 | version = "0.2.15" 2207 | source = "registry+https://github.com/rust-lang/crates.io-index" 2208 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 2209 | 2210 | [[package]] 2211 | name = "version_check" 2212 | version = "0.9.5" 2213 | source = "registry+https://github.com/rust-lang/crates.io-index" 2214 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 2215 | 2216 | [[package]] 2217 | name = "want" 2218 | version = "0.3.1" 2219 | source = "registry+https://github.com/rust-lang/crates.io-index" 2220 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 2221 | dependencies = [ 2222 | "try-lock", 2223 | ] 2224 | 2225 | [[package]] 2226 | name = "wasi" 2227 | version = "0.11.0+wasi-snapshot-preview1" 2228 | source = "registry+https://github.com/rust-lang/crates.io-index" 2229 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2230 | 2231 | [[package]] 2232 | name = "wasm-bindgen" 2233 | version = "0.2.95" 2234 | source = "registry+https://github.com/rust-lang/crates.io-index" 2235 | checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" 2236 | dependencies = [ 2237 | "cfg-if", 2238 | "once_cell", 2239 | "wasm-bindgen-macro", 2240 | ] 2241 | 2242 | [[package]] 2243 | name = "wasm-bindgen-backend" 2244 | version = "0.2.95" 2245 | source = "registry+https://github.com/rust-lang/crates.io-index" 2246 | checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" 2247 | dependencies = [ 2248 | "bumpalo", 2249 | "log", 2250 | "once_cell", 2251 | "proc-macro2", 2252 | "quote", 2253 | "syn", 2254 | "wasm-bindgen-shared", 2255 | ] 2256 | 2257 | [[package]] 2258 | name = "wasm-bindgen-futures" 2259 | version = "0.4.45" 2260 | source = "registry+https://github.com/rust-lang/crates.io-index" 2261 | checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" 2262 | dependencies = [ 2263 | "cfg-if", 2264 | "js-sys", 2265 | "wasm-bindgen", 2266 | "web-sys", 2267 | ] 2268 | 2269 | [[package]] 2270 | name = "wasm-bindgen-macro" 2271 | version = "0.2.95" 2272 | source = "registry+https://github.com/rust-lang/crates.io-index" 2273 | checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" 2274 | dependencies = [ 2275 | "quote", 2276 | "wasm-bindgen-macro-support", 2277 | ] 2278 | 2279 | [[package]] 2280 | name = "wasm-bindgen-macro-support" 2281 | version = "0.2.95" 2282 | source = "registry+https://github.com/rust-lang/crates.io-index" 2283 | checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" 2284 | dependencies = [ 2285 | "proc-macro2", 2286 | "quote", 2287 | "syn", 2288 | "wasm-bindgen-backend", 2289 | "wasm-bindgen-shared", 2290 | ] 2291 | 2292 | [[package]] 2293 | name = "wasm-bindgen-shared" 2294 | version = "0.2.95" 2295 | source = "registry+https://github.com/rust-lang/crates.io-index" 2296 | checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" 2297 | 2298 | [[package]] 2299 | name = "web-sys" 2300 | version = "0.3.72" 2301 | source = "registry+https://github.com/rust-lang/crates.io-index" 2302 | checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" 2303 | dependencies = [ 2304 | "js-sys", 2305 | "wasm-bindgen", 2306 | ] 2307 | 2308 | [[package]] 2309 | name = "winapi" 2310 | version = "0.3.9" 2311 | source = "registry+https://github.com/rust-lang/crates.io-index" 2312 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2313 | dependencies = [ 2314 | "winapi-i686-pc-windows-gnu", 2315 | "winapi-x86_64-pc-windows-gnu", 2316 | ] 2317 | 2318 | [[package]] 2319 | name = "winapi-i686-pc-windows-gnu" 2320 | version = "0.4.0" 2321 | source = "registry+https://github.com/rust-lang/crates.io-index" 2322 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2323 | 2324 | [[package]] 2325 | name = "winapi-util" 2326 | version = "0.1.9" 2327 | source = "registry+https://github.com/rust-lang/crates.io-index" 2328 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 2329 | dependencies = [ 2330 | "windows-sys 0.59.0", 2331 | ] 2332 | 2333 | [[package]] 2334 | name = "winapi-x86_64-pc-windows-gnu" 2335 | version = "0.4.0" 2336 | source = "registry+https://github.com/rust-lang/crates.io-index" 2337 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2338 | 2339 | [[package]] 2340 | name = "windows-core" 2341 | version = "0.52.0" 2342 | source = "registry+https://github.com/rust-lang/crates.io-index" 2343 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 2344 | dependencies = [ 2345 | "windows-targets 0.52.6", 2346 | ] 2347 | 2348 | [[package]] 2349 | name = "windows-sys" 2350 | version = "0.48.0" 2351 | source = "registry+https://github.com/rust-lang/crates.io-index" 2352 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 2353 | dependencies = [ 2354 | "windows-targets 0.48.5", 2355 | ] 2356 | 2357 | [[package]] 2358 | name = "windows-sys" 2359 | version = "0.52.0" 2360 | source = "registry+https://github.com/rust-lang/crates.io-index" 2361 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2362 | dependencies = [ 2363 | "windows-targets 0.52.6", 2364 | ] 2365 | 2366 | [[package]] 2367 | name = "windows-sys" 2368 | version = "0.59.0" 2369 | source = "registry+https://github.com/rust-lang/crates.io-index" 2370 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2371 | dependencies = [ 2372 | "windows-targets 0.52.6", 2373 | ] 2374 | 2375 | [[package]] 2376 | name = "windows-targets" 2377 | version = "0.48.5" 2378 | source = "registry+https://github.com/rust-lang/crates.io-index" 2379 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 2380 | dependencies = [ 2381 | "windows_aarch64_gnullvm 0.48.5", 2382 | "windows_aarch64_msvc 0.48.5", 2383 | "windows_i686_gnu 0.48.5", 2384 | "windows_i686_msvc 0.48.5", 2385 | "windows_x86_64_gnu 0.48.5", 2386 | "windows_x86_64_gnullvm 0.48.5", 2387 | "windows_x86_64_msvc 0.48.5", 2388 | ] 2389 | 2390 | [[package]] 2391 | name = "windows-targets" 2392 | version = "0.52.6" 2393 | source = "registry+https://github.com/rust-lang/crates.io-index" 2394 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2395 | dependencies = [ 2396 | "windows_aarch64_gnullvm 0.52.6", 2397 | "windows_aarch64_msvc 0.52.6", 2398 | "windows_i686_gnu 0.52.6", 2399 | "windows_i686_gnullvm", 2400 | "windows_i686_msvc 0.52.6", 2401 | "windows_x86_64_gnu 0.52.6", 2402 | "windows_x86_64_gnullvm 0.52.6", 2403 | "windows_x86_64_msvc 0.52.6", 2404 | ] 2405 | 2406 | [[package]] 2407 | name = "windows_aarch64_gnullvm" 2408 | version = "0.48.5" 2409 | source = "registry+https://github.com/rust-lang/crates.io-index" 2410 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2411 | 2412 | [[package]] 2413 | name = "windows_aarch64_gnullvm" 2414 | version = "0.52.6" 2415 | source = "registry+https://github.com/rust-lang/crates.io-index" 2416 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2417 | 2418 | [[package]] 2419 | name = "windows_aarch64_msvc" 2420 | version = "0.48.5" 2421 | source = "registry+https://github.com/rust-lang/crates.io-index" 2422 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2423 | 2424 | [[package]] 2425 | name = "windows_aarch64_msvc" 2426 | version = "0.52.6" 2427 | source = "registry+https://github.com/rust-lang/crates.io-index" 2428 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2429 | 2430 | [[package]] 2431 | name = "windows_i686_gnu" 2432 | version = "0.48.5" 2433 | source = "registry+https://github.com/rust-lang/crates.io-index" 2434 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2435 | 2436 | [[package]] 2437 | name = "windows_i686_gnu" 2438 | version = "0.52.6" 2439 | source = "registry+https://github.com/rust-lang/crates.io-index" 2440 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2441 | 2442 | [[package]] 2443 | name = "windows_i686_gnullvm" 2444 | version = "0.52.6" 2445 | source = "registry+https://github.com/rust-lang/crates.io-index" 2446 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2447 | 2448 | [[package]] 2449 | name = "windows_i686_msvc" 2450 | version = "0.48.5" 2451 | source = "registry+https://github.com/rust-lang/crates.io-index" 2452 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2453 | 2454 | [[package]] 2455 | name = "windows_i686_msvc" 2456 | version = "0.52.6" 2457 | source = "registry+https://github.com/rust-lang/crates.io-index" 2458 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2459 | 2460 | [[package]] 2461 | name = "windows_x86_64_gnu" 2462 | version = "0.48.5" 2463 | source = "registry+https://github.com/rust-lang/crates.io-index" 2464 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2465 | 2466 | [[package]] 2467 | name = "windows_x86_64_gnu" 2468 | version = "0.52.6" 2469 | source = "registry+https://github.com/rust-lang/crates.io-index" 2470 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2471 | 2472 | [[package]] 2473 | name = "windows_x86_64_gnullvm" 2474 | version = "0.48.5" 2475 | source = "registry+https://github.com/rust-lang/crates.io-index" 2476 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2477 | 2478 | [[package]] 2479 | name = "windows_x86_64_gnullvm" 2480 | version = "0.52.6" 2481 | source = "registry+https://github.com/rust-lang/crates.io-index" 2482 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2483 | 2484 | [[package]] 2485 | name = "windows_x86_64_msvc" 2486 | version = "0.48.5" 2487 | source = "registry+https://github.com/rust-lang/crates.io-index" 2488 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2489 | 2490 | [[package]] 2491 | name = "windows_x86_64_msvc" 2492 | version = "0.52.6" 2493 | source = "registry+https://github.com/rust-lang/crates.io-index" 2494 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2495 | 2496 | [[package]] 2497 | name = "winnow" 2498 | version = "0.5.40" 2499 | source = "registry+https://github.com/rust-lang/crates.io-index" 2500 | checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" 2501 | dependencies = [ 2502 | "memchr", 2503 | ] 2504 | 2505 | [[package]] 2506 | name = "winreg" 2507 | version = "0.50.0" 2508 | source = "registry+https://github.com/rust-lang/crates.io-index" 2509 | checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 2510 | dependencies = [ 2511 | "cfg-if", 2512 | "windows-sys 0.48.0", 2513 | ] 2514 | 2515 | [[package]] 2516 | name = "wyz" 2517 | version = "0.5.1" 2518 | source = "registry+https://github.com/rust-lang/crates.io-index" 2519 | checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" 2520 | dependencies = [ 2521 | "tap", 2522 | ] 2523 | 2524 | [[package]] 2525 | name = "zerocopy" 2526 | version = "0.7.35" 2527 | source = "registry+https://github.com/rust-lang/crates.io-index" 2528 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 2529 | dependencies = [ 2530 | "byteorder", 2531 | "zerocopy-derive", 2532 | ] 2533 | 2534 | [[package]] 2535 | name = "zerocopy-derive" 2536 | version = "0.7.35" 2537 | source = "registry+https://github.com/rust-lang/crates.io-index" 2538 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 2539 | dependencies = [ 2540 | "proc-macro2", 2541 | "quote", 2542 | "syn", 2543 | ] 2544 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "moccasin" 3 | version = "0.1.3" 4 | edition = "2021" 5 | authors = ["Tobias Fried "] 6 | license = "MIT" 7 | description = "A TUI feed reader for RSS, Atom, and (aspirationally) Podcasts" 8 | readme = "README.md" 9 | homepage = "https://github.com/rektdeckard/moccasin" 10 | repository = "https://github.com/rektdeckard/moccasin" 11 | keywords = ["rss", "atom", "feed-reader", "tui"] 12 | categories = ["command-line-utilities"] 13 | 14 | [[bin]] 15 | name = "mcsn" 16 | path = "src/main.rs" 17 | 18 | [dependencies] 19 | anyhow = "1" 20 | chrono = "0.4" 21 | clap = { version = "4.4", features = ["derive"] } 22 | colorsys = "0.6" 23 | crossterm = "0.27" 24 | directories = "5" 25 | futures = "0.3" 26 | html-escape = "0.2" 27 | html_parser = "0.7.0" 28 | log = "0.4.20" 29 | polodb_core = "4.4.0" 30 | reqwest = { version = "0.11", features = ["blocking"] } 31 | rss = { version = "2", features = ["atom"] } 32 | rusqlite = { version = "0.29.0", features = ["bundled", "serde_json"] } 33 | serde = { version = "1", features = ["derive"] } 34 | simplelog = "0.12.1" 35 | tokio = { version = "1", features = ["full"] } 36 | toml = "0.7" 37 | toml_edit = "0.19" 38 | tui = { package = "ratatui", version = "0.22", features = ["all-widgets"] } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Tobias Fried 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # moccasin 2 | 3 | A TUI feed reader for RSS, Atom, and (eventually) Podcasts. VIM keybindings. Ranger-inspired interface. Configurable. 4 | 5 | ![Crates.io (version)](https://img.shields.io/crates/v/moccasin.svg?style=flat-square) 6 | ![CI status](https://img.shields.io/github/actions/workflow/status/rektdeckard/moccasin/vhs.yaml?style=flat-square) 7 | 8 | [![GitHub stars](https://img.shields.io/github/stars/rektdeckard/moccasin?style=flat-square&label=Star)](https://github.com/rektdeckard/moccasin) 9 | [![GitHub forks](https://img.shields.io/github/forks/rektdeckard/moccasin?style=flat-square&label=Fork)](https://github.com/rektdeckard/moccasin/fork) 10 | [![GitHub watchers](https://img.shields.io/github/watchers/rektdeckard/moccasin?style=flat-square&label=Watch)](https://github.com/rektdeckard/moccasin) 11 | [![Follow on GitHub](https://img.shields.io/github/followers/rektdeckard?style=flat-square&label=Follow)](https://github.com/rektdeckard) 12 | 13 | ![tabs TUI in action](https://github.com/rektdeckard/moccasin/blob/main/meta/vhs.gif?raw=true) 14 | 15 | ## Installation 16 | 17 | ```bash 18 | cargo install moccasin 19 | ``` 20 | 21 | ### NetBSD 22 | 23 | If you are on NetBSD, a pre-compiled binary is available from the official repositories. To install it, simply run: 24 | 25 | ```bash 26 | pkgin install moccasin 27 | ``` 28 | 29 | Or, if you prefer to build from source: 30 | ``` 31 | cd /usr/pkgsrc/news/moccasin 32 | make install 33 | ``` 34 | 35 | ### Nix 36 | 37 | moccasin is available as a Nix flake: `github:rektdeckard/moccasin`. 38 | 39 | ## Usage 40 | 41 | Since "moccasin" is hard to spell and has too many letters, the executable is just called `mcsn`. 42 | 43 | ```bash 44 | mcsn [OPTIONS] 45 | ``` 46 | 47 | ### Options 48 | 49 | Command line arguments will override any values set in your [config file](#moccasintoml) for that session. 50 | 51 | | Short | Long | Args | Description | 52 | | ----- | ---------------- | ---------------- | ------------------------------------------------------------------------------------------------------- | 53 | | `-c` | `--config` | \ | Set a custom config file | 54 | | `-s` | `--color-scheme` | \ | Set a color scheme, either [built-in](#moccasintoml) or a path to a [custom theme](#color-schemes) file | 55 | | `-i` | `--interval` | \ | Set a custom refresh rate in seconds | 56 | | `-t` | `--timeout` | \ | Set a custom request timeout in seconds | 57 | | `-n` | `--no-cache` | | Do not cache feeds in local file-backed database | 58 | | `-h` | `--help` | | Print help | 59 | | `-V` | `--version` | | Print version | 60 | 61 | ## Config 62 | 63 | On first boot, Moccasin will create both a database and a config file in your default config directory, which varies by platform: 64 | 65 | | Platform | Value | Example | 66 | | -------- | ---------------------------------------------------------- | --------------------------------------------------------------- | 67 | | Linux | `$HOME`/.config/moccasin/ | /home/alice/.config/moccasin/ | 68 | | macOS | `$HOME`/Library/Application Support/com.rektsoft.moccasin/ | /Users/Alice/Library/Application Support/com.rektsoft.moccasin/ | 69 | | Windows | `{FOLDERID_LocalAppData}`\\rektsoft\moccasin\\config | C:\Users\Alice\AppData\Local\rektsoft\moccasin\config | 70 | 71 | The `moccasin.toml` file in this directory can be edited to customize app behavior, add feeds in bulk, change the color scheme, etc. Most of these properties can be changed from within the application as well, which will write to this file. Configuration options are as follows: 72 | 73 | ### `moccasin.toml` 74 | 75 | | Table | Field | Type | Default | Description | 76 | | --------------- | ------------------ | ------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 77 | | `[sources]` | | Table | | | 78 | | | `feeds` | Array | `[]` | URLs of Atom/RSS feeds you wish to see in-app. | 79 | | `[preferences]` | | Table | | | 80 | | | `color_scheme` | Enum \| Table | `"default"` | Either a built-in color scheme name, one of `"default"` \| `"borland"` \| `"darcula"` \| `"focus"` \| `"jungle"` \| `"matrix"` \| `"redshift"` \| `"wyse"`, or a table of values described [below](#color-schemes). | 81 | | | `sort_feeds` | Enum | `"a-z"` | Order in which to list feeds, one of `"a-z"` \| `"z-a"` \| `"newest"` \| `"oldest"` \| `"unread"` \| `"custom"` | 82 | | | `cache_feeds` | Boolean | `true` | Whether or not to write feeds to a local database for faster startup and access. When `false`, the app will use an in-memory database. | 83 | | | `refresh_interval` | Integer | `3600` | How often to refetch feeds, in seconds. | 84 | | | `refresh_timeout` | Integer | `5` | How long to wait for each feed before aborting, in seconds. | 85 | 86 | ### Color Schemes 87 | 88 | To create a custom color scheme, the `color_scheme` field can be declared as a table in which the keys are interface elements and the values are either a built-in ANSI color (which will inherit from your terminal emulator), a HEX color, or in InlineTable with `fg` and `bg` properties of the same type. 89 | 90 | ```toml 91 | [preferences.color_scheme] 92 | base = { fg = "white", bg = "#000080" } 93 | status = { fg = "gray", bg = "#000080" } 94 | border = "gray" 95 | selection_active = { fg = "#000080", bg = "#fefd72" } 96 | scrollbar = { fg = "white", bg = "gray" } 97 | ``` 98 | 99 | The built-in color names are 100 | 101 | - `"white"` 102 | - `"black"` 103 | - `"red"` 104 | - `"green"` 105 | - `"yellow"` 106 | - `"blue"` 107 | - `"magenta"` 108 | - `"cyan"` 109 | - `"gray"` 110 | - `"lightred"` 111 | - `"lightgreen"` 112 | - `"lightyellow"` 113 | - `"lightblue"` 114 | - `"lightmagenta"` 115 | - `"lightcyan"` 116 | - `"lightblack"` | `"darkgray"` 117 | 118 | The styleable properties are all optional, inheriting sensible defaults. Available properties are as follows: 119 | 120 | | Field | Default | Description | 121 | | ------------------ | ------------------ | --------------------------------------------- | 122 | | `base` | _terminal default_ | Base foreground and background colors | 123 | | `overlay` | `base` | Modal overlays | 124 | | `status` | `base` | The top menu bar and bottom status bar colors | 125 | | `selection` | `~base` | Selected list item | 126 | | `selection_active` | `selection` | Selected list item of active panel | 127 | | `border` | `border_active`\* | Border and titles around panels | 128 | | `border_active` | `base` | Border and title of active panel | 129 | | `scrollbar` | `base` | Thumb (`fg`) and track (`bg`) of scrollbars | 130 | 131 | > \* NOTE: it is important to define `border` when the style it inherits (either `base` or `border_active`) is defined as a hex color, otherwise it will be difficult to know which panel is currently active. 132 | 133 | ## Keybinds 134 | 135 | The application uses VIM-style keybinds, but arrow keys can also be used for navigation. At the moment, the app has a `NORMAL` mode and a `COMMAND` mode. In future, you should also be able to tag and group feeds and items in `GROUP` mode. 136 | 137 | ### NORMAL mode 138 | 139 | | Keys | Description | 140 | | ----------- | --------------------------------- | 141 | | `j`/`k` | Focus next/previous item | 142 | | `h`/`l` | Focus previous/next panel | 143 | | `Enter` | Select current item | 144 | | `Esc` | Deselect current item/mode | 145 | | `Tab` | Cycle tabs | 146 | | `b`/`f`/`t` | View Browse/Favorites/Tags tab | 147 | | `r` | Refresh all feeds | 148 | | `o` | Open current feed/item in browser | 149 | | `:` | Enter `COMMAND` mode | 150 | | `,` | Open config file | 151 | | `?` | Show keybinds | 152 | 153 | ### COMMAND mode 154 | 155 | | Command | Args | Description | 156 | | --------------- | -------- | ------------------------------------------------------------------------------------------------------ | 157 | | `:a`, `:add` | \ | Add a feed | 158 | | `:d`, `:delete` | [URL] | Delete feed for `URL`, or current feed if not supplied. Removes this entry from config file and cache. | 159 | | `:s`, `:search` | \ | Search for a feed, item, or text content | 160 | 161 | ## License 162 | 163 | MIT © [Tobias Fried](https://github.com/rektdeckard) 164 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "naersk": { 22 | "inputs": { 23 | "nixpkgs": "nixpkgs" 24 | }, 25 | "locked": { 26 | "lastModified": 1721727458, 27 | "narHash": "sha256-r/xppY958gmZ4oTfLiHN0ZGuQ+RSTijDblVgVLFi1mw=", 28 | "owner": "nix-community", 29 | "repo": "naersk", 30 | "rev": "3fb418eaf352498f6b6c30592e3beb63df42ef11", 31 | "type": "github" 32 | }, 33 | "original": { 34 | "owner": "nix-community", 35 | "repo": "naersk", 36 | "type": "github" 37 | } 38 | }, 39 | "nixpkgs": { 40 | "locked": { 41 | "lastModified": 1723991338, 42 | "narHash": "sha256-Grh5PF0+gootJfOJFenTTxDTYPidA3V28dqJ/WV7iis=", 43 | "path": "/nix/store/d9gbq853jvbccrz5g3y0irbwgc57w137-source", 44 | "rev": "8a3354191c0d7144db9756a74755672387b702ba", 45 | "type": "path" 46 | }, 47 | "original": { 48 | "id": "nixpkgs", 49 | "type": "indirect" 50 | } 51 | }, 52 | "nixpkgs_2": { 53 | "locked": { 54 | "lastModified": 1732238832, 55 | "narHash": "sha256-sQxuJm8rHY20xq6Ah+GwIUkF95tWjGRd1X8xF+Pkk38=", 56 | "owner": "NixOS", 57 | "repo": "nixpkgs", 58 | "rev": "8edf06bea5bcbee082df1b7369ff973b91618b8d", 59 | "type": "github" 60 | }, 61 | "original": { 62 | "owner": "NixOS", 63 | "ref": "nixpkgs-unstable", 64 | "repo": "nixpkgs", 65 | "type": "github" 66 | } 67 | }, 68 | "root": { 69 | "inputs": { 70 | "flake-utils": "flake-utils", 71 | "naersk": "naersk", 72 | "nixpkgs": "nixpkgs_2" 73 | } 74 | }, 75 | "systems": { 76 | "locked": { 77 | "lastModified": 1681028828, 78 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 79 | "owner": "nix-systems", 80 | "repo": "default", 81 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 82 | "type": "github" 83 | }, 84 | "original": { 85 | "owner": "nix-systems", 86 | "repo": "default", 87 | "type": "github" 88 | } 89 | } 90 | }, 91 | "root": "root", 92 | "version": 7 93 | } 94 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 4 | flake-utils.url = "github:numtide/flake-utils"; 5 | naersk.url = "github:nix-community/naersk"; 6 | }; 7 | 8 | outputs = 9 | { 10 | self, 11 | flake-utils, 12 | naersk, 13 | nixpkgs, 14 | }: 15 | flake-utils.lib.eachDefaultSystem ( 16 | system: 17 | let 18 | pkgs = (import nixpkgs) { inherit system; }; 19 | naersk' = pkgs.callPackage naersk { }; 20 | in 21 | { 22 | # For `nix build` & `nix run`: 23 | defaultPackage = naersk'.buildPackage { src = ./.; }; 24 | 25 | # For `nix develop`: 26 | devShell = pkgs.mkShell { 27 | nativeBuildInputs = with pkgs; [ 28 | rustc 29 | cargo 30 | rust-analyzer 31 | clippy 32 | rustfmt 33 | ]; 34 | }; 35 | } 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /meta/vhs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rektdeckard/moccasin/95d223518f2a12bb516f29cf394ca47decd6ec9b/meta/vhs.gif -------------------------------------------------------------------------------- /src/app.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Config; 2 | use crate::feed::{Feed, Item}; 3 | use crate::repo::{Repository, RepositoryEvent}; 4 | use anyhow::Result; 5 | use clap::Parser; 6 | use std::error; 7 | use std::process::{Child, Command, Stdio}; 8 | use std::str::FromStr; 9 | use std::task::Poll; 10 | use tokio::sync::mpsc::{self, UnboundedReceiver}; 11 | use tui::widgets::{ListState, ScrollbarState}; 12 | 13 | #[derive(Parser, Debug)] 14 | #[command(author, version, about, long_about = None)] 15 | pub struct Args { 16 | /// Set a custom config file 17 | #[arg(short, long)] 18 | pub config: Option, 19 | 20 | /// Set a custom theme, either built-in or a path to a theme file 21 | #[arg(short = 's', long)] 22 | pub color_scheme: Option, 23 | 24 | /// Set a custom refresh rate in seconds 25 | #[arg(short, long)] 26 | pub interval: Option, 27 | 28 | /// Set a custom request timeout in seconds 29 | #[arg(short, long)] 30 | pub timeout: Option, 31 | 32 | /// Do not cache feeds in local file-backed database 33 | #[arg(short, long)] 34 | pub no_cache: bool, 35 | } 36 | 37 | /// Application result type. 38 | pub type AppResult = std::result::Result>; 39 | 40 | #[derive(Debug)] 41 | pub enum Status { 42 | Loading(usize, usize), 43 | Errored(String), 44 | Done, 45 | } 46 | 47 | #[derive(Debug)] 48 | pub enum ConsoleCommand { 49 | AddFeed(String), 50 | DeleteFeed(Option), 51 | Search(String), 52 | } 53 | 54 | #[derive(Debug)] 55 | pub enum ConsoleCommandError { 56 | BadCommand, 57 | BadArgument, 58 | } 59 | 60 | impl FromStr for ConsoleCommand { 61 | type Err = ConsoleCommandError; 62 | 63 | fn from_str(s: &str) -> std::result::Result { 64 | let parts = s.split_whitespace().collect::>(); 65 | 66 | if let Some(cmd) = parts.get(0) { 67 | match *cmd { 68 | ":a" | ":add" => match parts.get(1) { 69 | Some(url) => Ok(ConsoleCommand::AddFeed(url.to_string())), 70 | None => Err(ConsoleCommandError::BadArgument), 71 | }, 72 | ":s" | ":search" => { 73 | let query = parts.iter().skip(1).copied().collect::(); 74 | if query.is_empty() { 75 | Err(ConsoleCommandError::BadArgument) 76 | } else { 77 | Ok(ConsoleCommand::Search(query)) 78 | } 79 | } 80 | ":d" | ":delete" => match parts.get(1) { 81 | Some(url) => Ok(ConsoleCommand::DeleteFeed(Some(url.to_string()))), 82 | None => Ok(ConsoleCommand::DeleteFeed(None)), 83 | }, 84 | _ => Err(ConsoleCommandError::BadCommand), 85 | } 86 | } else { 87 | Err(ConsoleCommandError::BadCommand) 88 | } 89 | } 90 | } 91 | 92 | /// Application. 93 | #[derive(Debug)] 94 | pub struct App { 95 | pub config: Config, 96 | pub repo: Repository, 97 | pub running: bool, 98 | pub active_view: View, 99 | pub active_tab: Tab, 100 | pub feeds: StatefulList, 101 | pub feeds_scroll: ScrollbarState, 102 | pub items: StatefulList, 103 | pub items_scroll: ScrollbarState, 104 | pub detail_scroll: ScrollbarState, 105 | pub detail_scroll_index: u16, 106 | pub show_keybinds: bool, 107 | pub status: Status, 108 | pub command_state: InputState, 109 | dimensions: (u16, u16), 110 | repo_rx: UnboundedReceiver, 111 | } 112 | 113 | impl App { 114 | pub fn init(dimensions: (u16, u16)) -> Result { 115 | let args = Args::parse(); 116 | let config = Config::new(args)?; 117 | 118 | let (tx, rx) = mpsc::unbounded_channel::(); 119 | let mut repo = Repository::init(&config, tx)?; 120 | 121 | let items = repo.read_all(&config).unwrap_or_default(); 122 | let feeds_count = items.len() as u16; 123 | 124 | Ok(Self { 125 | config, 126 | repo, 127 | running: true, 128 | dimensions, 129 | active_view: View::MainList, 130 | active_tab: Tab::Browse, 131 | feeds: StatefulList::::with_items(items), 132 | feeds_scroll: ScrollbarState::default().content_length(feeds_count), 133 | items: StatefulList::::default(), 134 | items_scroll: ScrollbarState::default(), 135 | detail_scroll: ScrollbarState::default(), 136 | detail_scroll_index: 0, 137 | status: Status::Done, 138 | show_keybinds: false, 139 | command_state: InputState::new(), 140 | repo_rx: rx, 141 | }) 142 | } 143 | 144 | /// Handles the tick event of the terminal. 145 | pub fn tick(&mut self) { 146 | self.repo.tick(&self.config); 147 | 148 | let waker = futures::task::noop_waker(); 149 | let mut cx = std::task::Context::from_waker(&waker); 150 | 151 | loop { 152 | match self.repo_rx.poll_recv(&mut cx) { 153 | Poll::Ready(m) => match m { 154 | Some(RepositoryEvent::Requesting(amount)) => { 155 | self.status = match self.status { 156 | Status::Loading(curr, total) => Status::Loading(curr, total + amount), 157 | _ => Status::Loading(0, amount), 158 | }; 159 | } 160 | Some(RepositoryEvent::Requested(counts)) => { 161 | let counts = match self.status { 162 | Status::Loading(current, total) => ((current + 1).min(total), total), 163 | _ => counts, 164 | }; 165 | self.status = Status::Loading(counts.0, counts.1); 166 | } 167 | Some(RepositoryEvent::RetrievedAll(feeds)) => { 168 | self.set_feeds(feeds); 169 | self.status = Status::Done; 170 | break; 171 | } 172 | Some(RepositoryEvent::RetrievedOne(feed)) => { 173 | match self 174 | .feeds 175 | .items 176 | .iter() 177 | .enumerate() 178 | .find(|(_, f)| f.link() == feed.link()) 179 | { 180 | Some((i, f)) => { 181 | self.feeds.items[i] = f.clone(); 182 | } 183 | None => { 184 | self.feeds.items.push(feed); 185 | } 186 | } 187 | 188 | match self.status { 189 | Status::Loading(_, _) => { 190 | self.status = Status::Done; 191 | } 192 | _ => {} 193 | } 194 | 195 | break; 196 | } 197 | Some(RepositoryEvent::Errored) => { 198 | self.status = Status::Errored("database transaction failed".into()); 199 | break; 200 | } 201 | Some(RepositoryEvent::Refresh) => {} 202 | Some(RepositoryEvent::Aborted) => { 203 | self.status = Status::Done; 204 | break; 205 | } 206 | None => { 207 | break; 208 | } 209 | }, 210 | Poll::Pending => { 211 | break; 212 | } 213 | } 214 | } 215 | } 216 | 217 | /// Set running to false to quit the application. 218 | pub fn quit(&mut self) { 219 | self.running = false; 220 | } 221 | 222 | pub fn set_dimensions(&mut self, dimensions: (u16, u16)) { 223 | self.dimensions = dimensions; 224 | } 225 | 226 | pub fn should_render_feeds_scroll(&self) -> bool { 227 | self.feeds.items().len() as u16 > self.dimensions.1 - 8 228 | } 229 | 230 | pub fn should_render_items_scroll(&self) -> bool { 231 | self.items.items().len() as u16 > self.dimensions.1 - 8 232 | } 233 | 234 | pub fn should_render_detail_scroll(&self) -> bool { 235 | // TODO 236 | false 237 | } 238 | 239 | pub fn should_render_console(&self) -> bool { 240 | self.command_state.show_input 241 | } 242 | 243 | pub fn current_feed(&self) -> Option<&Feed> { 244 | self.feeds 245 | .state 246 | .selected() 247 | .and_then(|i| self.feeds.items().get(i)) 248 | } 249 | 250 | pub fn current_item(&self) -> Option<&Item> { 251 | self.items 252 | .state 253 | .selected() 254 | .and_then(|i| self.items.items().get(i)) 255 | } 256 | 257 | pub fn next_feed(&mut self) { 258 | self.feeds.next(); 259 | self.feeds_scroll = self.feeds_scroll.position( 260 | self.feeds 261 | .state 262 | .selected() 263 | .unwrap_or(self.feeds.state.offset()) as u16, 264 | ); 265 | 266 | if let Some(channel) = self.current_feed() { 267 | self.items.items = channel.items().into(); 268 | self.items_scroll = self 269 | .items_scroll 270 | .content_length(self.items.items.len() as u16); 271 | } 272 | } 273 | 274 | pub fn prev_feed(&mut self) { 275 | self.feeds.previous(); 276 | self.feeds_scroll = self.feeds_scroll.position( 277 | self.feeds 278 | .state 279 | .selected() 280 | .unwrap_or(self.feeds.state.offset()) as u16, 281 | ); 282 | 283 | if let Some(channel) = self.current_feed() { 284 | self.items.items = channel.items().into(); 285 | self.items_scroll = self 286 | .items_scroll 287 | .content_length(self.items.items.len() as u16); 288 | } 289 | } 290 | 291 | pub fn next_item(&mut self) { 292 | self.items.next(); 293 | self.items_scroll = self.items_scroll.position( 294 | self.items 295 | .state 296 | .selected() 297 | .unwrap_or(self.items.state.offset()) as u16, 298 | ); 299 | } 300 | 301 | pub fn prev_item(&mut self) { 302 | self.items.previous(); 303 | self.items_scroll = self.items_scroll.position( 304 | self.items 305 | .state 306 | .selected() 307 | .unwrap_or(self.items.state.offset()) as u16, 308 | ); 309 | } 310 | 311 | pub fn next_view(&mut self, wrap: bool) { 312 | let has_current_feed = self.current_feed().is_some(); 313 | let has_current_item = self.current_item().is_some(); 314 | 315 | if !has_current_feed { 316 | self.active_view = View::MainList; 317 | return; 318 | } 319 | 320 | if let Some(next_view) = match self.active_view { 321 | View::MainList => { 322 | if self.items.state.selected().is_none() { 323 | self.next_item(); 324 | } 325 | Some(View::SubList) 326 | } 327 | View::SubList => { 328 | if has_current_item { 329 | Some(View::Detail) 330 | } else if wrap { 331 | Some(View::MainList) 332 | } else { 333 | None 334 | } 335 | } 336 | View::Detail => { 337 | if wrap { 338 | Some(View::MainList) 339 | } else { 340 | None 341 | } 342 | } 343 | } { 344 | self.active_view = next_view; 345 | } 346 | } 347 | 348 | pub fn prev_view(&mut self, wrap: bool) { 349 | let has_current_feed = self.current_feed().is_some(); 350 | let has_current_item = self.current_item().is_some(); 351 | 352 | if !has_current_feed { 353 | self.active_view = View::MainList; 354 | return; 355 | } 356 | 357 | if let Some(next_view) = match self.active_view { 358 | View::MainList => { 359 | if wrap && has_current_item { 360 | Some(View::Detail) 361 | } else if wrap { 362 | Some(View::SubList) 363 | } else { 364 | None 365 | } 366 | } 367 | View::SubList => Some(View::MainList), 368 | View::Detail => Some(View::SubList), 369 | } { 370 | self.active_view = next_view; 371 | } 372 | } 373 | 374 | pub fn next(&mut self) { 375 | match self.active_view { 376 | View::MainList => { 377 | self.reset_items_scroll(); 378 | self.reset_detail_scroll(); 379 | self.next_feed(); 380 | } 381 | View::SubList => { 382 | self.reset_detail_scroll(); 383 | self.next_item(); 384 | } 385 | View::Detail => { 386 | self.detail_scroll_index = self.detail_scroll_index.saturating_add(1); 387 | self.detail_scroll.next(); 388 | } 389 | } 390 | } 391 | 392 | pub fn prev(&mut self) { 393 | match self.active_view { 394 | View::MainList => { 395 | self.reset_items_scroll(); 396 | self.reset_detail_scroll(); 397 | self.prev_feed(); 398 | } 399 | View::SubList => { 400 | self.reset_detail_scroll(); 401 | self.prev_item(); 402 | } 403 | View::Detail => { 404 | self.detail_scroll_index = self.detail_scroll_index.saturating_sub(1); 405 | self.detail_scroll.prev(); 406 | } 407 | } 408 | } 409 | 410 | pub fn next_tab(&mut self) { 411 | let next_tab = match self.active_tab { 412 | Tab::Browse => Tab::Favorites, 413 | Tab::Favorites => Tab::Tags, 414 | Tab::Tags => Tab::Browse, 415 | }; 416 | 417 | self.active_tab = next_tab; 418 | } 419 | 420 | pub fn prev_tab(&mut self) { 421 | let prev_tab = match self.active_tab { 422 | Tab::Browse => Tab::Tags, 423 | Tab::Favorites => Tab::Browse, 424 | Tab::Tags => Tab::Favorites, 425 | }; 426 | 427 | self.active_tab = prev_tab; 428 | } 429 | 430 | pub fn set_tab(&mut self, index: usize) { 431 | self.active_tab = Tab::from(index); 432 | } 433 | 434 | pub fn unselect(&mut self) { 435 | if self.current_item().is_some() { 436 | self.items.state.select(None); 437 | } else { 438 | self.feeds.state.select(None); 439 | } 440 | self.prev_view(false); 441 | } 442 | 443 | pub fn open(&mut self) { 444 | match self.active_view { 445 | View::MainList => { 446 | if let Some(feed) = self.current_feed() { 447 | let link = feed.link(); 448 | let _ = App::open_link(link); 449 | } 450 | } 451 | View::SubList => { 452 | if let Some(item) = self.current_item() { 453 | if let Some(link) = item.link() { 454 | let _ = App::open_link(link); 455 | } 456 | } 457 | } 458 | _ => {} 459 | } 460 | } 461 | 462 | pub fn open_config(&self) -> Option { 463 | if let Some(cfg_path) = self.config.config_file_path().as_path().to_str() { 464 | Self::open_link(cfg_path) 465 | } else { 466 | None 467 | } 468 | } 469 | 470 | pub fn refresh_all(&mut self) { 471 | self.repo.refresh_all(&self.config) 472 | } 473 | 474 | pub fn toggle_keybinds(&mut self) { 475 | self.show_keybinds = !self.show_keybinds; 476 | } 477 | 478 | pub fn toggle_console(&mut self, cmd: Option<&str>) { 479 | if let Some(cmd) = cmd { 480 | self.command_state.input = cmd.into(); 481 | self.command_state.cursor_position = self.clamp_cursor(cmd.len()); 482 | } else { 483 | self.command_state.input.clear(); 484 | self.reset_cursor(); 485 | } 486 | self.command_state.show_input = !self.command_state.show_input; 487 | } 488 | 489 | pub fn move_cursor_left(&mut self) { 490 | let cursor_moved_left = self.command_state.cursor_position.saturating_sub(1); 491 | self.command_state.cursor_position = self.clamp_cursor(cursor_moved_left); 492 | } 493 | 494 | pub fn move_cursor_right(&mut self) { 495 | let cursor_moved_right = self.command_state.cursor_position.saturating_add(1); 496 | self.command_state.cursor_position = self.clamp_cursor(cursor_moved_right); 497 | } 498 | 499 | pub fn enter_char(&mut self, new_char: char) { 500 | self.command_state 501 | .input 502 | .insert(self.command_state.cursor_position, new_char); 503 | self.move_cursor_right(); 504 | } 505 | 506 | pub fn delete_char(&mut self) { 507 | let is_not_cursor_leftmost = self.command_state.cursor_position != 0; 508 | if is_not_cursor_leftmost { 509 | // Method "remove" is not used on the saved text for deleting the selected char. 510 | // Reason: Using remove on String works on bytes instead of the chars. 511 | // Using remove would require special care because of char boundaries. 512 | 513 | let current_index = self.command_state.cursor_position; 514 | let from_left_to_current_index = current_index - 1; 515 | 516 | // Getting all characters before the selected character. 517 | let before_char_to_delete = self 518 | .command_state 519 | .input 520 | .chars() 521 | .take(from_left_to_current_index); 522 | // Getting all characters after selected character. 523 | let after_char_to_delete = self.command_state.input.chars().skip(current_index); 524 | 525 | // Put all characters together except the selected one. 526 | // By leaving the selected one out, it is forgotten and therefore deleted. 527 | self.command_state.input = before_char_to_delete.chain(after_char_to_delete).collect(); 528 | self.move_cursor_left(); 529 | } 530 | } 531 | 532 | fn clamp_cursor(&self, new_cursor_pos: usize) -> usize { 533 | new_cursor_pos.clamp(0, self.command_state.input.len()) 534 | } 535 | 536 | fn reset_cursor(&mut self) { 537 | self.command_state.cursor_position = 0; 538 | } 539 | 540 | pub fn submit_command(&mut self) { 541 | match self.command_state.input.parse::() { 542 | Ok(ConsoleCommand::AddFeed(url)) => { 543 | self.config.add_feed_url(&url); 544 | self.repo.add_feed_url(&url, &self.config); 545 | } 546 | Ok(ConsoleCommand::DeleteFeed(maybe_url)) => { 547 | if let Some(url) = 548 | maybe_url.or(self.current_feed().and_then(|f| Some(f.url().into()))) 549 | { 550 | self.config.remove_feed_url(&url); 551 | self.repo.remove_feed_url(&url); 552 | 553 | // TODO: refactor, this is so bad 554 | self.feeds.items.retain(|u| u.url() != url); 555 | self.feeds.state.select(None); 556 | self.reset_items_scroll(); 557 | self.reset_detail_scroll(); 558 | } 559 | } 560 | Ok(ConsoleCommand::Search(_)) => todo!(), 561 | _ => self.status = Status::Errored("unrecognized command".into()), 562 | } 563 | 564 | self.command_state.input.clear(); 565 | self.reset_cursor(); 566 | self.toggle_console(None); 567 | } 568 | 569 | fn set_feeds(&mut self, feeds: Vec) { 570 | self.feeds.items = feeds; 571 | // self.items.state.select(None); 572 | // self.active_view = ActiveView::Feeds; 573 | } 574 | 575 | fn reset_items_scroll(&mut self) { 576 | self.items.state.select(None); 577 | self.items_scroll = self.items_scroll.position(0); 578 | } 579 | 580 | fn reset_detail_scroll(&mut self) { 581 | self.detail_scroll_index = 0; 582 | self.detail_scroll = self.detail_scroll.position(0); 583 | } 584 | 585 | fn open_link(link: &str) -> Option { 586 | let null = Stdio::null(); 587 | if cfg!(target_os = "windows") { 588 | Command::new("rundll32") 589 | .args(["url.dll,FileProtocolHandler", link]) 590 | .stdout(null) 591 | .spawn() 592 | .ok() 593 | } else if cfg!(target_os = "macos") { 594 | Command::new("open").arg(link).stdout(null).spawn().ok() 595 | } else if cfg!(target_os = "linux") { 596 | Command::new("xdg-open").arg(link).stdout(null).spawn().ok() 597 | } else { 598 | None 599 | } 600 | } 601 | } 602 | 603 | #[derive(Debug, PartialEq)] 604 | pub enum View { 605 | MainList, 606 | SubList, 607 | Detail, 608 | } 609 | 610 | #[derive(Debug, PartialEq)] 611 | pub enum Tab { 612 | Browse, 613 | Favorites, 614 | Tags, 615 | } 616 | 617 | impl ToString for Tab { 618 | fn to_string(&self) -> String { 619 | match self { 620 | Self::Browse => "Browse".into(), 621 | Self::Favorites => "Favorites".into(), 622 | Self::Tags => "Tags".into(), 623 | } 624 | } 625 | } 626 | 627 | impl Tab { 628 | pub fn index_of(&self) -> usize { 629 | match self { 630 | Self::Browse => 0, 631 | Self::Favorites => 1, 632 | Self::Tags => 2, 633 | } 634 | } 635 | } 636 | 637 | impl From for Tab { 638 | fn from(value: usize) -> Self { 639 | match value { 640 | 1 => Tab::Favorites, 641 | 2 => Tab::Tags, 642 | _ => Tab::Browse, 643 | } 644 | } 645 | } 646 | 647 | #[derive(Default, Debug)] 648 | pub struct StatefulList { 649 | pub state: ListState, 650 | pub items: Vec, 651 | } 652 | 653 | impl StatefulList { 654 | fn with_items(items: Vec) -> StatefulList { 655 | StatefulList { 656 | state: ListState::default(), 657 | items, 658 | } 659 | } 660 | 661 | fn next(&mut self) { 662 | if self.items.len() == 0 { 663 | return; 664 | } 665 | 666 | let i = match self.state.selected() { 667 | Some(i) => { 668 | if i >= self.items.len() - 1 { 669 | 0 670 | } else { 671 | i + 1 672 | } 673 | } 674 | None => 0, 675 | }; 676 | self.state.select(Some(i)); 677 | } 678 | 679 | fn previous(&mut self) { 680 | if self.items.len() == 0 { 681 | return; 682 | } 683 | 684 | let i = match self.state.selected() { 685 | Some(i) => { 686 | if i <= 0 { 687 | self.items.len() - 1 688 | } else { 689 | i - 1 690 | } 691 | } 692 | None => 0, 693 | }; 694 | self.state.select(Some(i)); 695 | } 696 | 697 | #[allow(dead_code)] 698 | fn unselect(&mut self) { 699 | self.state.select(None); 700 | } 701 | 702 | pub fn items(&self) -> &Vec { 703 | &self.items 704 | } 705 | } 706 | 707 | #[derive(Debug)] 708 | pub struct InputState { 709 | pub input: String, 710 | pub cursor_position: usize, 711 | show_input: bool, 712 | } 713 | 714 | impl InputState { 715 | fn new() -> Self { 716 | Self { 717 | input: String::new(), 718 | cursor_position: 0, 719 | show_input: false, 720 | } 721 | } 722 | } 723 | -------------------------------------------------------------------------------- /src/config/moccasin.toml: -------------------------------------------------------------------------------- 1 | # moccasin config 2 | # This file may be modified by moccasin when changes are made via software, 3 | # E.G. importing OPML files or individual feeds and changing colorschemes. 4 | 5 | [sources] 6 | # List URLs for Atom or RSS feeds here 7 | feeds = [ 8 | "https://bigthink.com/feed/all/", 9 | "https://feeds.feedburner.com/brainpickings/rss", 10 | "https://feeds.simplecast.com/dLRotFGk", 11 | "https://alistapart.com/main/feed/", 12 | ] 13 | 14 | [preferences] 15 | # The TUI color scheme, either a built-in scheme name: 16 | # "default" | "borland" | "darcula" | "focus" | "jungle" | "matrix" | "redshift" | "wyse" 17 | # or a table of key-value pairs, in which value is either a hex color string, 18 | # a built-in color string, one of: 19 | # | "black" | "red" | "yellow" | "blue" | "magenta" | "cyan" | "gray" 20 | # | "darkgray" | "lightred" | "lightgreen" | "lightyellow" | "lightblue" 21 | # | "lightmagenta" | "lightcyan" | "white" 22 | # or an inline table of fg and bg colors of the same type: 23 | # [preferences.color_scheme] 24 | # name = "borland" 25 | # base = { fg = "#FFFFFF", bg = "#000080" } 26 | # overlay = { fg = "#000080", bg = "#bbbbbb" } 27 | # status = { fg = "#bbbbbb", bg = "#000080" } 28 | # border = "#bbbbbb" 29 | # border_active = "#FFFFFF" 30 | # selection = { fg = "#000080", bg = "#bbbbbb" } 31 | # selection_active = { fg = "#000080", bg = "#fefd72" } 32 | # scrollbar = { fg = "#FFFFFF", bg = "#bbbbbb" } 33 | # All values are optional, and will inherit sensible defaults if omitted. 34 | color_scheme = "default" 35 | 36 | # The default sort order of feeds in the left panel, one of: 37 | # "a-z" | "z-a" | "newest" | "oldest" | "unread" | "custom" 38 | # where "custom" is the order listed in [sources.feeds], and "unread" is sorted 39 | # first by unread, then by newest. 40 | sort_feeds = "a-z" 41 | 42 | # Whether or not to cache feeds in a local file-backed database 43 | cache_feeds = true 44 | 45 | # How often to refresh feeds, in seconds. 46 | # The default is 3600 (1 hour), and 0 means refresh is always manual. 47 | refresh_interval = 3600 48 | 49 | # How long to wait on a given feed before timing out, in seconds 50 | refresh_timeout = 10 51 | -------------------------------------------------------------------------------- /src/config/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::app::Args; 2 | use anyhow::Result; 3 | use directories::ProjectDirs; 4 | use std::collections::HashSet; 5 | use std::fs::OpenOptions; 6 | use std::io::Write; 7 | use std::path::{Path, PathBuf}; 8 | use std::str::FromStr; 9 | use std::{fs, fs::File}; 10 | use toml::{Table, Value}; 11 | use toml_edit::{value, Array, Document}; 12 | 13 | mod theme; 14 | 15 | const DEFAULT_CONFIG_FILE: &'static str = "moccasin.toml"; 16 | const DEFAULT_DB_FILE: &'static str = "moccasin.db"; 17 | const DEFAULT_REFRESH_INTERVAL: u64 = 300; 18 | const DEFAULT_REFRESH_TIMEOUT: u64 = 5; 19 | 20 | #[derive(Debug, Default, Clone)] 21 | pub struct Config { 22 | file_path: PathBuf, 23 | dir_path: PathBuf, 24 | feed_urls: HashSet, 25 | sort_order: SortOrder, 26 | cache_control: CacheControl, 27 | refresh_interval: u64, 28 | refresh_timeout: u64, 29 | theme: theme::Theme, 30 | } 31 | 32 | #[derive(Debug, Default, Clone)] 33 | pub enum SortOrder { 34 | #[default] 35 | Az, 36 | Za, 37 | Unread, 38 | Newest, 39 | Oldest, 40 | Custom, 41 | } 42 | 43 | #[derive(Debug, Default, Clone, PartialEq)] 44 | pub enum CacheControl { 45 | #[default] 46 | Always, 47 | Never, 48 | } 49 | 50 | impl From for CacheControl { 51 | fn from(value: bool) -> Self { 52 | if value { 53 | Self::Always 54 | } else { 55 | Self::Never 56 | } 57 | } 58 | } 59 | 60 | #[derive(Debug)] 61 | pub struct SortOrderError; 62 | 63 | impl FromStr for SortOrder { 64 | type Err = SortOrderError; 65 | 66 | fn from_str(s: &str) -> std::result::Result { 67 | match s { 68 | "a-z" => Ok(SortOrder::Az), 69 | "z-a" => Ok(SortOrder::Za), 70 | "newest" => Ok(SortOrder::Newest), 71 | "oldest" => Ok(SortOrder::Oldest), 72 | "custom" => Ok(SortOrder::Custom), 73 | _ => Ok(SortOrder::Az), 74 | } 75 | } 76 | } 77 | 78 | impl Config { 79 | pub fn new(args: Args) -> Result { 80 | let (dir_path, file_path): (PathBuf, PathBuf) = if let Some(path) = &args.config { 81 | let file_path = Path::new(&path); 82 | if !file_path.exists() { 83 | panic!( 84 | "no config file found at '{}'", 85 | file_path.to_owned().to_str().unwrap() 86 | ) 87 | } 88 | 89 | let dir_path = file_path.parent().expect("could not find config directory"); 90 | (dir_path.into(), file_path.into()) 91 | } else { 92 | let dir_path = ProjectDirs::from("com", "rektsoft", "moccasin") 93 | .unwrap() 94 | .config_local_dir() 95 | .to_owned(); 96 | let file_path = dir_path.join(DEFAULT_CONFIG_FILE).to_owned(); 97 | fs::create_dir_all(&dir_path)?; 98 | (dir_path, file_path) 99 | }; 100 | 101 | if cfg!(debug_assertions) { 102 | dbg!(&dir_path.join("moccasin.log")); 103 | let file = OpenOptions::new() 104 | .create(true) 105 | .write(true) 106 | .append(true) 107 | .open(dir_path.join("moccasin.log")) 108 | .expect("could not open file for witing"); 109 | simplelog::WriteLogger::init( 110 | simplelog::LevelFilter::Info, 111 | simplelog::Config::default(), 112 | file, 113 | ) 114 | .expect("could not initialize logger"); 115 | } 116 | 117 | if file_path.exists() { 118 | Self::read_from_toml(args, dir_path, file_path) 119 | } else { 120 | Self::create_initialized(args, dir_path, file_path) 121 | } 122 | } 123 | 124 | pub fn config_dir_path(&self) -> PathBuf { 125 | Path::new(&self.dir_path).to_owned() 126 | } 127 | 128 | pub fn config_file_path(&self) -> PathBuf { 129 | Path::new(&self.file_path).to_owned() 130 | } 131 | 132 | pub fn db_path(&self) -> PathBuf { 133 | self.config_dir_path().join(DEFAULT_DB_FILE) 134 | } 135 | 136 | pub fn themes_path(&self) -> PathBuf { 137 | self.config_dir_path().join("themes") 138 | } 139 | 140 | pub fn theme(&self) -> &theme::Theme { 141 | &self.theme 142 | } 143 | 144 | pub fn feed_urls(&self) -> &HashSet { 145 | &self.feed_urls 146 | } 147 | 148 | pub fn sort_order(&self) -> &SortOrder { 149 | &self.sort_order 150 | } 151 | 152 | pub fn should_cache(&self) -> bool { 153 | self.cache_control == CacheControl::Always 154 | } 155 | 156 | pub fn refresh_interval(&self) -> u64 { 157 | self.refresh_interval 158 | } 159 | 160 | pub fn refresh_timeout(&self) -> u64 { 161 | self.refresh_timeout 162 | } 163 | 164 | pub fn write_config(&self) -> Result<()> { 165 | let toml = fs::read_to_string(&self.file_path)?; 166 | let mut toml = toml.parse::()?; 167 | 168 | let mut urls = Array::new(); 169 | for url in self.feed_urls() { 170 | urls.push_formatted(url.into()); 171 | } 172 | urls.set_trailing_comma(true); 173 | toml["sources"]["feeds"] = value(urls); 174 | 175 | let _ = fs::write(&self.file_path, toml.to_string())?; 176 | Ok(()) 177 | } 178 | 179 | pub fn add_feed_url(&mut self, url: &str) -> Result<()> { 180 | if !self.feed_urls().contains(url) { 181 | log::info!("Adding new feed for {}", url); 182 | self.feed_urls.insert(url.into()); 183 | self.write_config()?; 184 | } 185 | Ok(()) 186 | } 187 | 188 | pub fn remove_feed_url(&mut self, url: &str) -> Result<()> { 189 | if self.feed_urls().contains(url) { 190 | log::info!("Deleting feed for {}", url); 191 | self.feed_urls.remove(url); 192 | self.write_config()?; 193 | } 194 | Ok(()) 195 | } 196 | 197 | fn read_from_toml(args: Args, dir_path: PathBuf, file_path: PathBuf) -> Result { 198 | let toml = fs::read_to_string(&file_path)?; 199 | let table = toml.parse::()?; 200 | let feeds: HashSet = match table.get("sources") { 201 | Some(Value::Table(sources)) => match sources.get("feeds") { 202 | Some(Value::Array(els)) => els 203 | .iter() 204 | .filter_map(|v| v.as_str().and_then(|v| Some(v.to_owned()))) 205 | .collect(), 206 | Some(_) => { 207 | panic!("unexpected config entry for [sources].feeds") 208 | } 209 | _ => HashSet::new(), 210 | }, 211 | _ => panic!("unexpected config entry for [sources]"), 212 | }; 213 | 214 | let preferences = match table.get("preferences") { 215 | Some(Value::Table(prefs)) => Some(prefs), 216 | Some(_) => panic!("invalid config entry for [preferences]"), 217 | None => None, 218 | }; 219 | 220 | // TODO: load from args if present 221 | let theme = args 222 | .color_scheme 223 | .and_then(|scheme| theme::Theme::from_str(&scheme).ok()) 224 | .or(preferences.and_then(|prefs| { 225 | prefs 226 | .get("color_scheme") 227 | .and_then(|scheme| theme::Theme::try_from(scheme).ok()) 228 | })) 229 | .unwrap_or_default(); 230 | 231 | let sort_order: SortOrder = preferences 232 | .and_then(|prefs| { 233 | prefs.get("sort_feeds").and_then(|ord| match ord { 234 | Value::String(ord) => Some(SortOrder::from_str(ord).unwrap()), 235 | _ => None, 236 | }) 237 | }) 238 | .unwrap_or_default(); 239 | 240 | let refresh_interval = args 241 | .interval 242 | .or({ 243 | preferences.and_then(|prefs| { 244 | prefs.get("refresh_interval").and_then(|i| match i { 245 | Value::Integer(i) => Some(*i as u64), 246 | _ => None, 247 | }) 248 | }) 249 | }) 250 | .unwrap_or(DEFAULT_REFRESH_INTERVAL); 251 | 252 | let refresh_timeout = args 253 | .timeout 254 | .or({ 255 | preferences.and_then(|prefs| { 256 | prefs.get("refresh_timeout").and_then(|i| match i { 257 | Value::Integer(i) => Some(*i as u64), 258 | _ => None, 259 | }) 260 | }) 261 | }) 262 | .unwrap_or(DEFAULT_REFRESH_TIMEOUT); 263 | 264 | let cache_control = if args.no_cache { 265 | CacheControl::Never 266 | } else { 267 | preferences 268 | .and_then(|prefs| { 269 | prefs.get("cache_feeds").and_then(|i| match i { 270 | Value::Boolean(b) => Some(CacheControl::from(*b)), 271 | _ => None, 272 | }) 273 | }) 274 | .unwrap_or(CacheControl::Always) 275 | }; 276 | 277 | Ok(Self { 278 | file_path, 279 | dir_path, 280 | feed_urls: feeds, 281 | sort_order, 282 | cache_control, 283 | refresh_interval, 284 | refresh_timeout, 285 | theme, 286 | }) 287 | } 288 | 289 | fn create_initialized(args: Args, dir_path: PathBuf, file_path: PathBuf) -> Result { 290 | fs::create_dir_all(&dir_path)?; 291 | let cfg_path = Path::new(dir_path.as_path()).join(DEFAULT_CONFIG_FILE); 292 | let mut file = File::create(&cfg_path)?; 293 | let toml = include_str!("moccasin.toml"); 294 | let stub = toml.parse::
()?; 295 | let feed_urls = stub["sources"]["feeds"] 296 | .as_array() 297 | .expect("parse default feeds") 298 | .iter() 299 | .filter_map(Value::as_str) 300 | .map(String::from) 301 | .collect::>(); 302 | file.write(toml.as_bytes())?; 303 | 304 | // TODO: load theme from args if present 305 | Ok(Self { 306 | dir_path: dir_path.to_owned(), 307 | file_path: file_path.to_owned(), 308 | feed_urls, 309 | refresh_interval: args.interval.unwrap_or(DEFAULT_REFRESH_INTERVAL), 310 | ..Default::default() 311 | }) 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /src/config/theme.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::fmt; 3 | use std::{error::Error, str::FromStr}; 4 | use toml::Value; 5 | use tui::style::{Color, Modifier, Style, Stylize}; 6 | 7 | #[derive(Debug)] 8 | enum ParseColorError { 9 | InvalidFormat, 10 | #[allow(dead_code)] 11 | InvalidDigit, 12 | #[allow(dead_code)] 13 | Empty, 14 | } 15 | 16 | #[derive(Debug)] 17 | pub struct ParseThemeError; 18 | 19 | impl fmt::Display for ParseThemeError { 20 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 21 | write!(f, "error parsing theme") 22 | } 23 | } 24 | 25 | impl Error for ParseThemeError { 26 | fn source(&self) -> Option<&(dyn Error + 'static)> { 27 | None 28 | } 29 | 30 | fn description(&self) -> &str { 31 | "description() is deprecated; use Display" 32 | } 33 | 34 | fn cause(&self) -> Option<&dyn Error> { 35 | self.source() 36 | } 37 | } 38 | 39 | fn make_color(c: &str) -> Color { 40 | if let Ok(c) = colorsys::Rgb::from_hex_str(c) { 41 | Color::Rgb(c.red() as u8, c.green() as u8, c.blue() as u8) 42 | } else { 43 | Color::Reset 44 | } 45 | } 46 | 47 | #[derive(Debug, Clone)] 48 | pub struct Theme { 49 | base: Style, 50 | overlay: Option