├── .github └── workflows │ └── release.yaml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── cli.rs ├── constants.rs ├── format.rs ├── lang.rs └── main.rs /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | release: 3 | types: [created] 4 | 5 | jobs: 6 | release: 7 | permissions: write-all 8 | name: release ${{ matrix.target }} 9 | runs-on: ubuntu-latest 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | target: [x86_64-unknown-linux-musl] 14 | steps: 15 | - uses: actions/checkout@master 16 | - name: Compile and release 17 | uses: rust-build/rust-build.action@v1.4.3 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | with: 21 | RUSTTARGET: ${{ matrix.target }} 22 | TOOLCHAIN_VERSION: stable 23 | EXTRA_FILES: "README.md LICENSE" 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "android-tzdata" 22 | version = "0.1.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 25 | 26 | [[package]] 27 | name = "android_system_properties" 28 | version = "0.1.5" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 31 | dependencies = [ 32 | "libc", 33 | ] 34 | 35 | [[package]] 36 | name = "anstream" 37 | version = "0.6.12" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540" 40 | dependencies = [ 41 | "anstyle", 42 | "anstyle-parse", 43 | "anstyle-query", 44 | "anstyle-wincon", 45 | "colorchoice", 46 | "utf8parse", 47 | ] 48 | 49 | [[package]] 50 | name = "anstyle" 51 | version = "1.0.10" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 54 | 55 | [[package]] 56 | name = "anstyle-parse" 57 | version = "0.2.3" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" 60 | dependencies = [ 61 | "utf8parse", 62 | ] 63 | 64 | [[package]] 65 | name = "anstyle-query" 66 | version = "1.0.2" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" 69 | dependencies = [ 70 | "windows-sys 0.52.0", 71 | ] 72 | 73 | [[package]] 74 | name = "anstyle-wincon" 75 | version = "3.0.2" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" 78 | dependencies = [ 79 | "anstyle", 80 | "windows-sys 0.52.0", 81 | ] 82 | 83 | [[package]] 84 | name = "autocfg" 85 | version = "1.1.0" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 88 | 89 | [[package]] 90 | name = "backtrace" 91 | version = "0.3.69" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 94 | dependencies = [ 95 | "addr2line", 96 | "cc", 97 | "cfg-if", 98 | "libc", 99 | "miniz_oxide", 100 | "object", 101 | "rustc-demangle", 102 | ] 103 | 104 | [[package]] 105 | name = "base64" 106 | version = "0.22.1" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 109 | 110 | [[package]] 111 | name = "bumpalo" 112 | version = "3.15.3" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" 115 | 116 | [[package]] 117 | name = "byteorder" 118 | version = "1.5.0" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 121 | 122 | [[package]] 123 | name = "bytes" 124 | version = "1.8.0" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" 127 | 128 | [[package]] 129 | name = "cc" 130 | version = "1.0.88" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "02f341c093d19155a6e41631ce5971aac4e9a868262212153124c15fa22d1cdc" 133 | 134 | [[package]] 135 | name = "cfg-if" 136 | version = "1.0.0" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 139 | 140 | [[package]] 141 | name = "cfg_aliases" 142 | version = "0.2.1" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 145 | 146 | [[package]] 147 | name = "chrono" 148 | version = "0.4.38" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" 151 | dependencies = [ 152 | "android-tzdata", 153 | "iana-time-zone", 154 | "js-sys", 155 | "num-traits", 156 | "wasm-bindgen", 157 | "windows-targets 0.52.6", 158 | ] 159 | 160 | [[package]] 161 | name = "clap" 162 | version = "4.5.21" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" 165 | dependencies = [ 166 | "clap_builder", 167 | "clap_derive", 168 | ] 169 | 170 | [[package]] 171 | name = "clap_builder" 172 | version = "4.5.21" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" 175 | dependencies = [ 176 | "anstream", 177 | "anstyle", 178 | "clap_lex", 179 | "strsim", 180 | ] 181 | 182 | [[package]] 183 | name = "clap_derive" 184 | version = "4.5.18" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 187 | dependencies = [ 188 | "heck", 189 | "proc-macro2", 190 | "quote", 191 | "syn", 192 | ] 193 | 194 | [[package]] 195 | name = "clap_lex" 196 | version = "0.7.0" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" 199 | 200 | [[package]] 201 | name = "colorchoice" 202 | version = "1.0.0" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 205 | 206 | [[package]] 207 | name = "core-foundation-sys" 208 | version = "0.8.6" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" 211 | 212 | [[package]] 213 | name = "fnv" 214 | version = "1.0.7" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 217 | 218 | [[package]] 219 | name = "form_urlencoded" 220 | version = "1.2.1" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 223 | dependencies = [ 224 | "percent-encoding", 225 | ] 226 | 227 | [[package]] 228 | name = "futures-channel" 229 | version = "0.3.30" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 232 | dependencies = [ 233 | "futures-core", 234 | "futures-sink", 235 | ] 236 | 237 | [[package]] 238 | name = "futures-core" 239 | version = "0.3.30" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 242 | 243 | [[package]] 244 | name = "futures-io" 245 | version = "0.3.30" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 248 | 249 | [[package]] 250 | name = "futures-sink" 251 | version = "0.3.30" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 254 | 255 | [[package]] 256 | name = "futures-task" 257 | version = "0.3.30" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 260 | 261 | [[package]] 262 | name = "futures-util" 263 | version = "0.3.30" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 266 | dependencies = [ 267 | "futures-core", 268 | "futures-io", 269 | "futures-sink", 270 | "futures-task", 271 | "memchr", 272 | "pin-project-lite", 273 | "pin-utils", 274 | "slab", 275 | ] 276 | 277 | [[package]] 278 | name = "getrandom" 279 | version = "0.2.12" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" 282 | dependencies = [ 283 | "cfg-if", 284 | "libc", 285 | "wasi", 286 | ] 287 | 288 | [[package]] 289 | name = "gimli" 290 | version = "0.28.1" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 293 | 294 | [[package]] 295 | name = "heck" 296 | version = "0.5.0" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 299 | 300 | [[package]] 301 | name = "http" 302 | version = "1.1.0" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" 305 | dependencies = [ 306 | "bytes", 307 | "fnv", 308 | "itoa", 309 | ] 310 | 311 | [[package]] 312 | name = "http-body" 313 | version = "1.0.1" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 316 | dependencies = [ 317 | "bytes", 318 | "http", 319 | ] 320 | 321 | [[package]] 322 | name = "http-body-util" 323 | version = "0.1.2" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" 326 | dependencies = [ 327 | "bytes", 328 | "futures-util", 329 | "http", 330 | "http-body", 331 | "pin-project-lite", 332 | ] 333 | 334 | [[package]] 335 | name = "httparse" 336 | version = "1.8.0" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 339 | 340 | [[package]] 341 | name = "hyper" 342 | version = "1.5.0" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" 345 | dependencies = [ 346 | "bytes", 347 | "futures-channel", 348 | "futures-util", 349 | "http", 350 | "http-body", 351 | "httparse", 352 | "itoa", 353 | "pin-project-lite", 354 | "smallvec", 355 | "tokio", 356 | "want", 357 | ] 358 | 359 | [[package]] 360 | name = "hyper-rustls" 361 | version = "0.27.3" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" 364 | dependencies = [ 365 | "futures-util", 366 | "http", 367 | "hyper", 368 | "hyper-util", 369 | "rustls", 370 | "rustls-pki-types", 371 | "tokio", 372 | "tokio-rustls", 373 | "tower-service", 374 | "webpki-roots", 375 | ] 376 | 377 | [[package]] 378 | name = "hyper-util" 379 | version = "0.1.10" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" 382 | dependencies = [ 383 | "bytes", 384 | "futures-channel", 385 | "futures-util", 386 | "http", 387 | "http-body", 388 | "hyper", 389 | "pin-project-lite", 390 | "socket2", 391 | "tokio", 392 | "tower-service", 393 | "tracing", 394 | ] 395 | 396 | [[package]] 397 | name = "iana-time-zone" 398 | version = "0.1.60" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" 401 | dependencies = [ 402 | "android_system_properties", 403 | "core-foundation-sys", 404 | "iana-time-zone-haiku", 405 | "js-sys", 406 | "wasm-bindgen", 407 | "windows-core", 408 | ] 409 | 410 | [[package]] 411 | name = "iana-time-zone-haiku" 412 | version = "0.1.2" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 415 | dependencies = [ 416 | "cc", 417 | ] 418 | 419 | [[package]] 420 | name = "idna" 421 | version = "0.5.0" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 424 | dependencies = [ 425 | "unicode-bidi", 426 | "unicode-normalization", 427 | ] 428 | 429 | [[package]] 430 | name = "ipnet" 431 | version = "2.9.0" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" 434 | 435 | [[package]] 436 | name = "itoa" 437 | version = "1.0.10" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" 440 | 441 | [[package]] 442 | name = "js-sys" 443 | version = "0.3.68" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" 446 | dependencies = [ 447 | "wasm-bindgen", 448 | ] 449 | 450 | [[package]] 451 | name = "libc" 452 | version = "0.2.162" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" 455 | 456 | [[package]] 457 | name = "log" 458 | version = "0.4.20" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 461 | 462 | [[package]] 463 | name = "memchr" 464 | version = "2.7.1" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" 467 | 468 | [[package]] 469 | name = "mime" 470 | version = "0.3.17" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 473 | 474 | [[package]] 475 | name = "miniz_oxide" 476 | version = "0.7.2" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" 479 | dependencies = [ 480 | "adler", 481 | ] 482 | 483 | [[package]] 484 | name = "mio" 485 | version = "0.8.10" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" 488 | dependencies = [ 489 | "libc", 490 | "wasi", 491 | "windows-sys 0.48.0", 492 | ] 493 | 494 | [[package]] 495 | name = "num-traits" 496 | version = "0.2.18" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" 499 | dependencies = [ 500 | "autocfg", 501 | ] 502 | 503 | [[package]] 504 | name = "object" 505 | version = "0.32.2" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 508 | dependencies = [ 509 | "memchr", 510 | ] 511 | 512 | [[package]] 513 | name = "once_cell" 514 | version = "1.19.0" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 517 | 518 | [[package]] 519 | name = "percent-encoding" 520 | version = "2.3.1" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 523 | 524 | [[package]] 525 | name = "pin-project-lite" 526 | version = "0.2.13" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 529 | 530 | [[package]] 531 | name = "pin-utils" 532 | version = "0.1.0" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 535 | 536 | [[package]] 537 | name = "ppv-lite86" 538 | version = "0.2.20" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 541 | dependencies = [ 542 | "zerocopy", 543 | ] 544 | 545 | [[package]] 546 | name = "proc-macro2" 547 | version = "1.0.78" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" 550 | dependencies = [ 551 | "unicode-ident", 552 | ] 553 | 554 | [[package]] 555 | name = "quinn" 556 | version = "0.11.5" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" 559 | dependencies = [ 560 | "bytes", 561 | "pin-project-lite", 562 | "quinn-proto", 563 | "quinn-udp", 564 | "rustc-hash", 565 | "rustls", 566 | "socket2", 567 | "thiserror", 568 | "tokio", 569 | "tracing", 570 | ] 571 | 572 | [[package]] 573 | name = "quinn-proto" 574 | version = "0.11.8" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" 577 | dependencies = [ 578 | "bytes", 579 | "rand", 580 | "ring", 581 | "rustc-hash", 582 | "rustls", 583 | "slab", 584 | "thiserror", 585 | "tinyvec", 586 | "tracing", 587 | ] 588 | 589 | [[package]] 590 | name = "quinn-udp" 591 | version = "0.5.7" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da" 594 | dependencies = [ 595 | "cfg_aliases", 596 | "libc", 597 | "once_cell", 598 | "socket2", 599 | "tracing", 600 | "windows-sys 0.52.0", 601 | ] 602 | 603 | [[package]] 604 | name = "quote" 605 | version = "1.0.35" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 608 | dependencies = [ 609 | "proc-macro2", 610 | ] 611 | 612 | [[package]] 613 | name = "rand" 614 | version = "0.8.5" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 617 | dependencies = [ 618 | "libc", 619 | "rand_chacha", 620 | "rand_core", 621 | ] 622 | 623 | [[package]] 624 | name = "rand_chacha" 625 | version = "0.3.1" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 628 | dependencies = [ 629 | "ppv-lite86", 630 | "rand_core", 631 | ] 632 | 633 | [[package]] 634 | name = "rand_core" 635 | version = "0.6.4" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 638 | dependencies = [ 639 | "getrandom", 640 | ] 641 | 642 | [[package]] 643 | name = "reqwest" 644 | version = "0.12.9" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" 647 | dependencies = [ 648 | "base64", 649 | "bytes", 650 | "futures-channel", 651 | "futures-core", 652 | "futures-util", 653 | "http", 654 | "http-body", 655 | "http-body-util", 656 | "hyper", 657 | "hyper-rustls", 658 | "hyper-util", 659 | "ipnet", 660 | "js-sys", 661 | "log", 662 | "mime", 663 | "once_cell", 664 | "percent-encoding", 665 | "pin-project-lite", 666 | "quinn", 667 | "rustls", 668 | "rustls-pemfile", 669 | "rustls-pki-types", 670 | "serde", 671 | "serde_json", 672 | "serde_urlencoded", 673 | "sync_wrapper", 674 | "tokio", 675 | "tokio-rustls", 676 | "tower-service", 677 | "url", 678 | "wasm-bindgen", 679 | "wasm-bindgen-futures", 680 | "web-sys", 681 | "webpki-roots", 682 | "windows-registry", 683 | ] 684 | 685 | [[package]] 686 | name = "ring" 687 | version = "0.17.8" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" 690 | dependencies = [ 691 | "cc", 692 | "cfg-if", 693 | "getrandom", 694 | "libc", 695 | "spin", 696 | "untrusted", 697 | "windows-sys 0.52.0", 698 | ] 699 | 700 | [[package]] 701 | name = "rustc-demangle" 702 | version = "0.1.23" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 705 | 706 | [[package]] 707 | name = "rustc-hash" 708 | version = "2.0.0" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" 711 | 712 | [[package]] 713 | name = "rustls" 714 | version = "0.23.16" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" 717 | dependencies = [ 718 | "once_cell", 719 | "ring", 720 | "rustls-pki-types", 721 | "rustls-webpki", 722 | "subtle", 723 | "zeroize", 724 | ] 725 | 726 | [[package]] 727 | name = "rustls-pemfile" 728 | version = "2.2.0" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 731 | dependencies = [ 732 | "rustls-pki-types", 733 | ] 734 | 735 | [[package]] 736 | name = "rustls-pki-types" 737 | version = "1.10.0" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" 740 | 741 | [[package]] 742 | name = "rustls-webpki" 743 | version = "0.102.8" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" 746 | dependencies = [ 747 | "ring", 748 | "rustls-pki-types", 749 | "untrusted", 750 | ] 751 | 752 | [[package]] 753 | name = "ryu" 754 | version = "1.0.17" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" 757 | 758 | [[package]] 759 | name = "serde" 760 | version = "1.0.197" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" 763 | dependencies = [ 764 | "serde_derive", 765 | ] 766 | 767 | [[package]] 768 | name = "serde_derive" 769 | version = "1.0.197" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" 772 | dependencies = [ 773 | "proc-macro2", 774 | "quote", 775 | "syn", 776 | ] 777 | 778 | [[package]] 779 | name = "serde_json" 780 | version = "1.0.133" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" 783 | dependencies = [ 784 | "itoa", 785 | "memchr", 786 | "ryu", 787 | "serde", 788 | ] 789 | 790 | [[package]] 791 | name = "serde_urlencoded" 792 | version = "0.7.1" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 795 | dependencies = [ 796 | "form_urlencoded", 797 | "itoa", 798 | "ryu", 799 | "serde", 800 | ] 801 | 802 | [[package]] 803 | name = "slab" 804 | version = "0.4.9" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 807 | dependencies = [ 808 | "autocfg", 809 | ] 810 | 811 | [[package]] 812 | name = "smallvec" 813 | version = "1.13.2" 814 | source = "registry+https://github.com/rust-lang/crates.io-index" 815 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 816 | 817 | [[package]] 818 | name = "socket2" 819 | version = "0.5.6" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" 822 | dependencies = [ 823 | "libc", 824 | "windows-sys 0.52.0", 825 | ] 826 | 827 | [[package]] 828 | name = "spin" 829 | version = "0.9.8" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 832 | 833 | [[package]] 834 | name = "strsim" 835 | version = "0.11.0" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" 838 | 839 | [[package]] 840 | name = "subtle" 841 | version = "2.6.1" 842 | source = "registry+https://github.com/rust-lang/crates.io-index" 843 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 844 | 845 | [[package]] 846 | name = "syn" 847 | version = "2.0.51" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c" 850 | dependencies = [ 851 | "proc-macro2", 852 | "quote", 853 | "unicode-ident", 854 | ] 855 | 856 | [[package]] 857 | name = "sync_wrapper" 858 | version = "1.0.1" 859 | source = "registry+https://github.com/rust-lang/crates.io-index" 860 | checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" 861 | dependencies = [ 862 | "futures-core", 863 | ] 864 | 865 | [[package]] 866 | name = "thiserror" 867 | version = "1.0.65" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" 870 | dependencies = [ 871 | "thiserror-impl", 872 | ] 873 | 874 | [[package]] 875 | name = "thiserror-impl" 876 | version = "1.0.65" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" 879 | dependencies = [ 880 | "proc-macro2", 881 | "quote", 882 | "syn", 883 | ] 884 | 885 | [[package]] 886 | name = "tinyvec" 887 | version = "1.6.0" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 890 | dependencies = [ 891 | "tinyvec_macros", 892 | ] 893 | 894 | [[package]] 895 | name = "tinyvec_macros" 896 | version = "0.1.1" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 899 | 900 | [[package]] 901 | name = "tokio" 902 | version = "1.36.0" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" 905 | dependencies = [ 906 | "backtrace", 907 | "bytes", 908 | "libc", 909 | "mio", 910 | "pin-project-lite", 911 | "socket2", 912 | "windows-sys 0.48.0", 913 | ] 914 | 915 | [[package]] 916 | name = "tokio-rustls" 917 | version = "0.26.0" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" 920 | dependencies = [ 921 | "rustls", 922 | "rustls-pki-types", 923 | "tokio", 924 | ] 925 | 926 | [[package]] 927 | name = "tower-service" 928 | version = "0.3.2" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 931 | 932 | [[package]] 933 | name = "tracing" 934 | version = "0.1.40" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 937 | dependencies = [ 938 | "pin-project-lite", 939 | "tracing-core", 940 | ] 941 | 942 | [[package]] 943 | name = "tracing-core" 944 | version = "0.1.32" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 947 | dependencies = [ 948 | "once_cell", 949 | ] 950 | 951 | [[package]] 952 | name = "try-lock" 953 | version = "0.2.5" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 956 | 957 | [[package]] 958 | name = "unicode-bidi" 959 | version = "0.3.15" 960 | source = "registry+https://github.com/rust-lang/crates.io-index" 961 | checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" 962 | 963 | [[package]] 964 | name = "unicode-ident" 965 | version = "1.0.12" 966 | source = "registry+https://github.com/rust-lang/crates.io-index" 967 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 968 | 969 | [[package]] 970 | name = "unicode-normalization" 971 | version = "0.1.23" 972 | source = "registry+https://github.com/rust-lang/crates.io-index" 973 | checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" 974 | dependencies = [ 975 | "tinyvec", 976 | ] 977 | 978 | [[package]] 979 | name = "untrusted" 980 | version = "0.9.0" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 983 | 984 | [[package]] 985 | name = "url" 986 | version = "2.5.0" 987 | source = "registry+https://github.com/rust-lang/crates.io-index" 988 | checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" 989 | dependencies = [ 990 | "form_urlencoded", 991 | "idna", 992 | "percent-encoding", 993 | ] 994 | 995 | [[package]] 996 | name = "utf8parse" 997 | version = "0.2.1" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 1000 | 1001 | [[package]] 1002 | name = "want" 1003 | version = "0.3.1" 1004 | source = "registry+https://github.com/rust-lang/crates.io-index" 1005 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1006 | dependencies = [ 1007 | "try-lock", 1008 | ] 1009 | 1010 | [[package]] 1011 | name = "wasi" 1012 | version = "0.11.0+wasi-snapshot-preview1" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1015 | 1016 | [[package]] 1017 | name = "wasm-bindgen" 1018 | version = "0.2.91" 1019 | source = "registry+https://github.com/rust-lang/crates.io-index" 1020 | checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" 1021 | dependencies = [ 1022 | "cfg-if", 1023 | "wasm-bindgen-macro", 1024 | ] 1025 | 1026 | [[package]] 1027 | name = "wasm-bindgen-backend" 1028 | version = "0.2.91" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" 1031 | dependencies = [ 1032 | "bumpalo", 1033 | "log", 1034 | "once_cell", 1035 | "proc-macro2", 1036 | "quote", 1037 | "syn", 1038 | "wasm-bindgen-shared", 1039 | ] 1040 | 1041 | [[package]] 1042 | name = "wasm-bindgen-futures" 1043 | version = "0.4.41" 1044 | source = "registry+https://github.com/rust-lang/crates.io-index" 1045 | checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" 1046 | dependencies = [ 1047 | "cfg-if", 1048 | "js-sys", 1049 | "wasm-bindgen", 1050 | "web-sys", 1051 | ] 1052 | 1053 | [[package]] 1054 | name = "wasm-bindgen-macro" 1055 | version = "0.2.91" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" 1058 | dependencies = [ 1059 | "quote", 1060 | "wasm-bindgen-macro-support", 1061 | ] 1062 | 1063 | [[package]] 1064 | name = "wasm-bindgen-macro-support" 1065 | version = "0.2.91" 1066 | source = "registry+https://github.com/rust-lang/crates.io-index" 1067 | checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" 1068 | dependencies = [ 1069 | "proc-macro2", 1070 | "quote", 1071 | "syn", 1072 | "wasm-bindgen-backend", 1073 | "wasm-bindgen-shared", 1074 | ] 1075 | 1076 | [[package]] 1077 | name = "wasm-bindgen-shared" 1078 | version = "0.2.91" 1079 | source = "registry+https://github.com/rust-lang/crates.io-index" 1080 | checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" 1081 | 1082 | [[package]] 1083 | name = "web-sys" 1084 | version = "0.3.68" 1085 | source = "registry+https://github.com/rust-lang/crates.io-index" 1086 | checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" 1087 | dependencies = [ 1088 | "js-sys", 1089 | "wasm-bindgen", 1090 | ] 1091 | 1092 | [[package]] 1093 | name = "webpki-roots" 1094 | version = "0.26.6" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" 1097 | dependencies = [ 1098 | "rustls-pki-types", 1099 | ] 1100 | 1101 | [[package]] 1102 | name = "windows-core" 1103 | version = "0.52.0" 1104 | source = "registry+https://github.com/rust-lang/crates.io-index" 1105 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 1106 | dependencies = [ 1107 | "windows-targets 0.52.6", 1108 | ] 1109 | 1110 | [[package]] 1111 | name = "windows-registry" 1112 | version = "0.2.0" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" 1115 | dependencies = [ 1116 | "windows-result", 1117 | "windows-strings", 1118 | "windows-targets 0.52.6", 1119 | ] 1120 | 1121 | [[package]] 1122 | name = "windows-result" 1123 | version = "0.2.0" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" 1126 | dependencies = [ 1127 | "windows-targets 0.52.6", 1128 | ] 1129 | 1130 | [[package]] 1131 | name = "windows-strings" 1132 | version = "0.1.0" 1133 | source = "registry+https://github.com/rust-lang/crates.io-index" 1134 | checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" 1135 | dependencies = [ 1136 | "windows-result", 1137 | "windows-targets 0.52.6", 1138 | ] 1139 | 1140 | [[package]] 1141 | name = "windows-sys" 1142 | version = "0.48.0" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1145 | dependencies = [ 1146 | "windows-targets 0.48.5", 1147 | ] 1148 | 1149 | [[package]] 1150 | name = "windows-sys" 1151 | version = "0.52.0" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1154 | dependencies = [ 1155 | "windows-targets 0.52.6", 1156 | ] 1157 | 1158 | [[package]] 1159 | name = "windows-targets" 1160 | version = "0.48.5" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1163 | dependencies = [ 1164 | "windows_aarch64_gnullvm 0.48.5", 1165 | "windows_aarch64_msvc 0.48.5", 1166 | "windows_i686_gnu 0.48.5", 1167 | "windows_i686_msvc 0.48.5", 1168 | "windows_x86_64_gnu 0.48.5", 1169 | "windows_x86_64_gnullvm 0.48.5", 1170 | "windows_x86_64_msvc 0.48.5", 1171 | ] 1172 | 1173 | [[package]] 1174 | name = "windows-targets" 1175 | version = "0.52.6" 1176 | source = "registry+https://github.com/rust-lang/crates.io-index" 1177 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1178 | dependencies = [ 1179 | "windows_aarch64_gnullvm 0.52.6", 1180 | "windows_aarch64_msvc 0.52.6", 1181 | "windows_i686_gnu 0.52.6", 1182 | "windows_i686_gnullvm", 1183 | "windows_i686_msvc 0.52.6", 1184 | "windows_x86_64_gnu 0.52.6", 1185 | "windows_x86_64_gnullvm 0.52.6", 1186 | "windows_x86_64_msvc 0.52.6", 1187 | ] 1188 | 1189 | [[package]] 1190 | name = "windows_aarch64_gnullvm" 1191 | version = "0.48.5" 1192 | source = "registry+https://github.com/rust-lang/crates.io-index" 1193 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1194 | 1195 | [[package]] 1196 | name = "windows_aarch64_gnullvm" 1197 | version = "0.52.6" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1200 | 1201 | [[package]] 1202 | name = "windows_aarch64_msvc" 1203 | version = "0.48.5" 1204 | source = "registry+https://github.com/rust-lang/crates.io-index" 1205 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1206 | 1207 | [[package]] 1208 | name = "windows_aarch64_msvc" 1209 | version = "0.52.6" 1210 | source = "registry+https://github.com/rust-lang/crates.io-index" 1211 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1212 | 1213 | [[package]] 1214 | name = "windows_i686_gnu" 1215 | version = "0.48.5" 1216 | source = "registry+https://github.com/rust-lang/crates.io-index" 1217 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1218 | 1219 | [[package]] 1220 | name = "windows_i686_gnu" 1221 | version = "0.52.6" 1222 | source = "registry+https://github.com/rust-lang/crates.io-index" 1223 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1224 | 1225 | [[package]] 1226 | name = "windows_i686_gnullvm" 1227 | version = "0.52.6" 1228 | source = "registry+https://github.com/rust-lang/crates.io-index" 1229 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1230 | 1231 | [[package]] 1232 | name = "windows_i686_msvc" 1233 | version = "0.48.5" 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" 1235 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1236 | 1237 | [[package]] 1238 | name = "windows_i686_msvc" 1239 | version = "0.52.6" 1240 | source = "registry+https://github.com/rust-lang/crates.io-index" 1241 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1242 | 1243 | [[package]] 1244 | name = "windows_x86_64_gnu" 1245 | version = "0.48.5" 1246 | source = "registry+https://github.com/rust-lang/crates.io-index" 1247 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1248 | 1249 | [[package]] 1250 | name = "windows_x86_64_gnu" 1251 | version = "0.52.6" 1252 | source = "registry+https://github.com/rust-lang/crates.io-index" 1253 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1254 | 1255 | [[package]] 1256 | name = "windows_x86_64_gnullvm" 1257 | version = "0.48.5" 1258 | source = "registry+https://github.com/rust-lang/crates.io-index" 1259 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1260 | 1261 | [[package]] 1262 | name = "windows_x86_64_gnullvm" 1263 | version = "0.52.6" 1264 | source = "registry+https://github.com/rust-lang/crates.io-index" 1265 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1266 | 1267 | [[package]] 1268 | name = "windows_x86_64_msvc" 1269 | version = "0.48.5" 1270 | source = "registry+https://github.com/rust-lang/crates.io-index" 1271 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1272 | 1273 | [[package]] 1274 | name = "windows_x86_64_msvc" 1275 | version = "0.52.6" 1276 | source = "registry+https://github.com/rust-lang/crates.io-index" 1277 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1278 | 1279 | [[package]] 1280 | name = "wttrbar" 1281 | version = "0.12.0" 1282 | dependencies = [ 1283 | "chrono", 1284 | "clap", 1285 | "reqwest", 1286 | "serde_json", 1287 | ] 1288 | 1289 | [[package]] 1290 | name = "zerocopy" 1291 | version = "0.7.35" 1292 | source = "registry+https://github.com/rust-lang/crates.io-index" 1293 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 1294 | dependencies = [ 1295 | "byteorder", 1296 | "zerocopy-derive", 1297 | ] 1298 | 1299 | [[package]] 1300 | name = "zerocopy-derive" 1301 | version = "0.7.35" 1302 | source = "registry+https://github.com/rust-lang/crates.io-index" 1303 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 1304 | dependencies = [ 1305 | "proc-macro2", 1306 | "quote", 1307 | "syn", 1308 | ] 1309 | 1310 | [[package]] 1311 | name = "zeroize" 1312 | version = "1.8.1" 1313 | source = "registry+https://github.com/rust-lang/crates.io-index" 1314 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 1315 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wttrbar" 3 | version = "0.12.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | chrono = "0.4.38" 10 | clap = { version = "4.5.21", features = ["derive"] } 11 | reqwest = { version = "0.12.9", default-features = false, features = [ 12 | "blocking", 13 | "json", 14 | "rustls-tls", 15 | ] } 16 | serde_json = "1.0.133" 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Yo'av Moshe 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 |

2 | wttrbar 3 |

4 | 5 |

6 | a simple but detailed weather indicator for Waybar using wttr.in. 7 |

8 |

9 | 10 |

11 |
12 | 13 | ## Installation 14 | 15 | Compile yourself using `cargo build --release`, or download the precompiled binary from the [releases](https://github.com/bjesus/wttrbar/releases) page. 16 | 17 | For Arch Linux, use the [AUR](https://aur.archlinux.org/packages/wttrbar) package. 18 | 19 | For NixOS, use the [NixPkg](https://search.nixos.org/packages?channel=24.05&show=wttrbar&from=0&size=50&sort=relevance&type=packages&query=wttrbar) package. 20 | 21 | ## Usage 22 | 23 | - `--ampm` - display time in AM/PM format 24 | - `--location STRING` - pass a specific location to wttr.in 25 | - `--main-indicator` - decide which [`current_conditions` key](https://wttr.in/?format=j1) will be shown on waybar. defaults to `temp_C` 26 | - `--date-format` - defaults to `%Y-%m-%d`, formats the date next to the days. see [reference](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) 27 | - `--nerd` - use [nerd font](https://www.nerdfonts.com/) symbols instead of emojis 28 | - `--hide-conditions` - show a shorter descrpition next to each hour, like `7° Mist` instead of `7° Mist, Overcast 81%, Sunshine 17%, Frost 15%` 29 | - `--fahrenheit` - use fahrenheit instead of celsius 30 | - `--mph` - use mph instead of km/h for wind speed 31 | - `--custom-indicator STRING` - optional expression that will be shown instead of main indicator. [`current_conditions` and `nearest_area` keys](https://wttr.in/?format=j1) surrounded by {} can be used. For example, `"{ICON} {FeelsLikeC} ({areaName})"` will be transformed to `"text":"🌧️ -4 (Amsterdam)"` in the output 32 | - `--lang LANG` - set language (currently `en`, `de`, `pl`, `tr`, `fr`, `ru`, `zh`, `be`, `es`, `pt`, `it`, `ja`, `uk`, `sv`; submit a PR to add yours) 33 | - `--observation-time` - show the time the current weather conditions were measured 34 | 35 | e.g. `wttrbar --date-format "%m/%d" --location Paris --hide-conditions` 36 | 37 | ### Icons 38 | 39 | To display the weather icons correctly, you will need to have a font that supports emojis installed. The screenshot uses [Noto Emoji](https://github.com/googlefonts/noto-emoji), but you can use [other fonts](https://wiki.archlinux.org/title/fonts#Emoji_and_symbols) too. 40 | 41 | ## Waybar configuration 42 | 43 | Assuming `wttrbar` is in your path, it can be used like: 44 | 45 | ```json 46 | "custom/weather": { 47 | "format": "{}°", 48 | "tooltip": true, 49 | "interval": 3600, 50 | "exec": "wttrbar", 51 | "return-type": "json" 52 | }, 53 | ``` 54 | 55 | You can also then creating custom styling based on the current condition: 56 | 57 | ```css 58 | #custom-weather.sunny { 59 | background-color: yellow; 60 | } 61 | ``` 62 | 63 | ## Old version 64 | 65 | This code is based on my [old Python gist](https://gist.github.com/bjesus/f8db49e1434433f78e5200dc403d58a3) that was used for the same purpose. 66 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use crate::Lang; 2 | use clap::Parser; 3 | 4 | #[derive(Parser, Debug)] 5 | #[command(author = "Yo'av Moshe", 6 | version = None, 7 | about = "A simple but detailed weather indicator for Waybar using wttr.in", 8 | long_about = None) 9 | ] 10 | pub struct Args { 11 | #[arg( 12 | long, 13 | default_value = "temp_C", 14 | help = "decide which current_conditions key will be shown on waybar" 15 | )] 16 | pub main_indicator: String, 17 | 18 | #[arg( 19 | long, 20 | help = "optional expression that will be shown instead of main indicator. current_conditions keys surrounded by {} can be used. example:\n\ 21 | \"{ICON}{temp_C}({FeelsLikeC})\" will be transformed to \"text\":\"🌧️0(-4)\" in output" 22 | )] 23 | pub custom_indicator: Option, 24 | 25 | #[arg( 26 | long, 27 | default_value = "%Y-%m-%d", 28 | help = "formats the date next to the days. see https://docs.rs/chrono/latest/chrono/format/strftime/index.html" 29 | )] 30 | pub date_format: String, 31 | 32 | #[arg(long, help = "pass a specific location to wttr.in")] 33 | pub location: Option, 34 | 35 | #[arg( 36 | long, 37 | help = "shows the icon on the first line and temperature in a new line" 38 | )] 39 | pub vertical_view: bool, 40 | 41 | #[arg( 42 | long, 43 | help = "show a shorter description next to each hour, like 7° Mist instead of 7° Mist, Overcast 81%, Sunshine 17%, Frost 15%" 44 | )] 45 | pub hide_conditions: bool, 46 | 47 | #[arg(long, help = "display time in AM/PM format")] 48 | pub ampm: bool, 49 | 50 | #[arg(long, help = "use nerd font symbols instead of emojis")] 51 | pub nerd: bool, 52 | 53 | #[arg(long, help = "use fahrenheit instead of celsius")] 54 | pub fahrenheit: bool, 55 | 56 | #[arg(long, short, help = "use mph instead of km/h for wind speed")] 57 | pub mph: bool, 58 | 59 | #[arg(value_enum, short, long, help = "language to use")] 60 | pub lang: Option, 61 | 62 | #[arg(long, help = "show when the current weather conditions were measured")] 63 | pub observation_time: bool, 64 | } 65 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | pub const WEATHER_CODES: &[(i32, &str)] = &[ 2 | (113, "☀️"), // Sunny 3 | (116, "🌤️"), // Partly cloudly 4 | (119, "☁️"), // Cloudy 5 | (122, "🌥️"), // Very cloudy 6 | (143, "🌫️"), // Fog 7 | (176, "🌦️"), // Light showers 8 | (179, "🌧️"), // Light sleet showers 9 | (182, "🌧️"), // Light sleet 10 | (185, "🌧️"), // Light sleet 11 | (200, "🌩️"), // Thundery showers 12 | (227, "❄️"), // Light snow 13 | (230, "❄️"), // Heavy snow 14 | (248, "🌫️"), // Fog 15 | (260, "🌫️"), // Fog 16 | (263, "🌧️"), // Light showers 17 | (266, "🌧️"), // Light rain 18 | (281, "🌦️"), // Light sleet 19 | (284, "🌦️"), // Light sleet 20 | (293, "🌧️"), // Light rain 21 | (296, "🌧️"), // Light rain 22 | (299, "🌧️"), // Heavy showers 23 | (302, "🌧️"), // Heavy rain 24 | (305, "🌧️"), // Heavy showers 25 | (308, "🌧️"), // Heavy rain 26 | (311, "🌧️"), // Light sleet 27 | (314, "🌧️"), // Light sleet 28 | (317, "🌧️"), // Light sleet 29 | (320, "🌨️"), // Light snow 30 | (323, "🌨️"), // Light snow showers 31 | (326, "🌨️"), // Light snow showers 32 | (329, "🌨️"), // Heavy Snow 33 | (332, "🌨️"), // Heavy Snow 34 | (335, "🌨️"), // Heavy snow showers 35 | (338, "🌨️"), // Heavy snow 36 | (350, "🌨️"), // Light sleet 37 | (353, "🌧️"), // Light showers 38 | (356, "🌧️"), // Heavy showers 39 | (359, "🌧️"), // Heavy rain 40 | (362, "🌨️"), // Light sleet showers 41 | (365, "🌨️"), // Light sleet showers 42 | (368, "🌨️"), // Light snow showers 43 | (371, "🌨️"), // Heavy snow showers 44 | (374, "🌨️"), // Light sleet showers 45 | (377, "🌨️"), // Light sleet 46 | (386, "🌩️"), // Thundery showers 47 | (389, "🌨️"), // Thundery heavy rain 48 | (392, "🌨️"), // Thundery snow showers 49 | (395, "🌨️"), // Heavy snow showers 50 | (398, "🌨️"), // This is all the ones defined in the wttr.in source code, not sure what these are, but apparently some sort of rain. 51 | (401, "🌨️"), 52 | (404, "🌨️"), 53 | (407, "🌨️"), 54 | (410, "🌨️"), 55 | (413, "🌨️"), 56 | (416, "🌨️"), 57 | (419, "🌨️"), 58 | (422, "🌨️"), 59 | (425, "🌨️"), 60 | (428, "🌨️"), 61 | (431, "🌨️"), 62 | ]; 63 | 64 | pub const WEATHER_CODES_NERD: &[(i32, &str)] = &[ 65 | (113, "󰖙"), // Sunny 66 | (116, "󰖕"), // Partly cloudly 67 | (119, "󰼰"), // Cloudy 68 | (122, "󰖐"), // Very cloudy 69 | (143, "󰖑"), // Fog 70 | (176, "󰖗"), // Light showers 71 | (179, "󰙿"), // Light sleet showers 72 | (182, "󰙿"), // Light sleet 73 | (185, "󰙿"), // Light sleet 74 | (200, "󰙾"), // Thundery showers 75 | (227, "󰖘"), // Light snow 76 | (230, "󰼶"), // Heavy snow 77 | (248, "󰖑"), // Fog 78 | (260, "󰖑"), // Fog 79 | (263, "󰖗"), // Light showers 80 | (266, "󰖗"), // Light rain 81 | (281, "󰙿"), // Light sleet 82 | (284, "󰙿"), // Light sleet 83 | (293, "󰖗"), // Light rain 84 | (296, "󰖗"), // Light rain 85 | (299, "󰖖"), // Heavy showers 86 | (302, "󰖖"), // Heavy rain 87 | (305, "󰖖"), // Heavy showers 88 | (308, "󰖖"), // Heavy rain 89 | (311, "󰙿"), // Light sleet 90 | (314, "󰙿"), // Light sleet 91 | (317, "󰙿"), // Light sleet 92 | (320, "󰖘"), // Light snow 93 | (323, "󰖘"), // Light snow showers 94 | (326, "󰖘"), // Light snow showers 95 | (329, "󰼶"), // Heavy Snow 96 | (332, "󰼶"), // Heavy Snow 97 | (335, "󰼶"), // Heavy snow showers 98 | (338, "󰼶"), // Heavy snow 99 | (350, "󰙿"), // Light sleet 100 | (353, "󰖗"), // Light showers 101 | (356, "󰖖"), // Heavy showers 102 | (359, "󰖖"), // Heavy rain 103 | (362, "󰙿"), // Light sleet showers 104 | (365, "󰙿"), // Light sleet showers 105 | (368, "󰖘"), // Light snow showers 106 | (371, "󰼶"), // Heavy snow showers 107 | (374, "󰙿"), // Light sleet showers 108 | (377, "󰙿"), // Light sleet 109 | (386, "󰙾"), // Thundery showers 110 | (389, "󰙾"), // Thundery heavy rain 111 | (392, "󰙾"), // Thundery snow showers 112 | (395, "󰼶"), // Heavy snow showers 113 | (398, "󰖗"), // This is all the ones defined in the wttr.in source code, not sure what these are, but apparently some sort of rain. 114 | (401, "󰖗"), 115 | (404, "󰖗"), 116 | (407, "󰖗"), 117 | (410, "󰖗"), 118 | (413, "󰖗"), 119 | (416, "󰖗"), 120 | (419, "󰖗"), 121 | (422, "󰖗"), 122 | (425, "󰖗"), 123 | (428, "󰖗"), 124 | (431, "󰖗"), 125 | ]; 126 | 127 | 128 | pub const ICON_PLACEHOLDER: &str = "{ICON}"; 129 | -------------------------------------------------------------------------------- /src/format.rs: -------------------------------------------------------------------------------- 1 | use chrono::prelude::*; 2 | use serde_json::Value; 3 | use std::collections::HashMap; 4 | 5 | use crate::lang::Lang; 6 | use crate::ICON_PLACEHOLDER; 7 | 8 | pub fn format_time(time: &str, ampm: bool) -> String { 9 | let hour = time.replace("00", "").parse::().unwrap(); 10 | 11 | if ampm { 12 | let am_or_pm = if hour >= 12 { "pm" } else { "am" }; 13 | let hour12 = if hour == 0 || hour == 12 { 14 | 12 15 | } else { 16 | hour % 12 17 | }; 18 | format!("{: <4}", format!("{}{}", hour12, am_or_pm)) 19 | } else { 20 | format!("{:02}", hour) 21 | } 22 | } 23 | 24 | pub fn format_temp(temp: &str) -> String { 25 | format!("{: >3}°", temp) 26 | } 27 | 28 | pub fn format_chances(hour: &serde_json::Value, lang: &Lang) -> String { 29 | let chances: HashMap<&str, String> = [ 30 | ("chanceoffog", lang.fog()), 31 | ("chanceoffrost", lang.frost()), 32 | ("chanceofovercast", lang.overcast()), 33 | ("chanceofrain", lang.rain()), 34 | ("chanceofsnow", lang.snow()), 35 | ("chanceofsunshine", lang.sunshine()), 36 | ("chanceofthunder", lang.thunder()), 37 | ("chanceofwindy", lang.wind()), 38 | ] 39 | .iter() 40 | .cloned() 41 | .collect(); 42 | 43 | let mut conditions = vec![]; 44 | for (event, name) in chances.iter() { 45 | if let Some(chance) = hour[event].as_str() { 46 | if let Ok(chance_value) = chance.parse::() { 47 | if chance_value > 0 { 48 | conditions.push((name, chance_value)); 49 | } 50 | } 51 | } 52 | } 53 | conditions.sort_by_key(|&(_, chance_value)| std::cmp::Reverse(chance_value)); 54 | conditions 55 | .iter() 56 | .map(|&(name, chance_value)| format!("{} {}%", name, chance_value)) 57 | .collect::>() 58 | .join(", ") 59 | } 60 | 61 | pub fn format_ampm_time(day: &serde_json::Value, key: &str, ampm: bool) -> String { 62 | if ampm { 63 | day["astronomy"][0][key].as_str().unwrap().to_string() 64 | } else { 65 | NaiveTime::parse_from_str(day["astronomy"][0][key].as_str().unwrap(), "%I:%M %p") 66 | .unwrap() 67 | .format("%H:%M") 68 | .to_string() 69 | } 70 | } 71 | pub fn format_indicator( 72 | weather_conditions: &Value, 73 | area: &Value, 74 | expression: String, 75 | weather_icon: &&str, 76 | ) -> String { 77 | if !weather_conditions.is_object() { 78 | return String::new(); 79 | } 80 | 81 | let (weather_map, area_map) = match (weather_conditions.as_object(), area.as_object()) { 82 | (Some(w), Some(a)) => (w, a), 83 | _ => return String::new(), 84 | }; 85 | let mut combined_map = weather_map.clone(); 86 | combined_map.extend(area_map.clone()); 87 | 88 | let mut formatted_indicator = expression.to_string(); 89 | combined_map 90 | .iter() 91 | .map(|condition| ("{".to_owned() + condition.0 + "}", condition.1)) 92 | .for_each(|condition| { 93 | if formatted_indicator.contains(condition.0.as_str()) { 94 | let condition_value = if condition.1.is_array() { 95 | condition.1.as_array().and_then(|vec| { 96 | vec[0] 97 | .as_object() 98 | .and_then(|value_map| value_map["value"].as_str()) 99 | }) 100 | } else { 101 | condition.1.as_str() 102 | } 103 | .unwrap_or(""); 104 | formatted_indicator = 105 | formatted_indicator.replace(condition.0.as_str(), condition_value) 106 | } 107 | }); 108 | if formatted_indicator.contains(ICON_PLACEHOLDER) { 109 | formatted_indicator = formatted_indicator.replace(ICON_PLACEHOLDER, weather_icon) 110 | } 111 | formatted_indicator 112 | } 113 | -------------------------------------------------------------------------------- /src/lang.rs: -------------------------------------------------------------------------------- 1 | use clap::ValueEnum; 2 | 3 | #[derive(Debug, Clone, ValueEnum)] 4 | pub enum Lang { 5 | EN, 6 | DE, 7 | PL, 8 | RU, 9 | TR, 10 | FR, 11 | BE, 12 | ZH, 13 | ES, 14 | PT, 15 | IT, 16 | JA, 17 | UK, 18 | SV, 19 | } 20 | 21 | impl Lang { 22 | pub fn wttr_in_subdomain(&self) -> String { 23 | match &self { 24 | Self::EN => "wttr.in".to_string(), 25 | Self::DE => "de.wttr.in".to_string(), 26 | Self::PL => "pl.wttr.in".to_string(), 27 | Self::RU => "ru.wttr.in".to_string(), 28 | Self::TR => "tr.wttr.in".to_string(), 29 | Self::FR => "fr.wttr.in".to_string(), 30 | Self::BE => "be.wttr.in".to_string(), 31 | Self::ZH => "zh.wttr.in".to_string(), 32 | Self::ES => "es.wttr.in".to_string(), 33 | Self::PT => "pt.wttr.in".to_string(), 34 | Self::IT => "it.wttr.in".to_string(), 35 | Self::JA => "ja.wttr.in".to_string(), 36 | Self::UK => "uk.wttr.in".to_string(), 37 | Self::SV => "sv.wttr.in".to_string(), 38 | } 39 | } 40 | pub fn observation_time(&self) -> String { 41 | match &self { 42 | Self::EN => "Observed at".to_string(), 43 | Self::DE => "Beobachtet um".to_string(), 44 | Self::PL => "Zaobserwowano o".to_string(), 45 | Self::RU => "Наблюдается в".to_string(), 46 | Self::TR => "Gözlemlendi".to_string(), 47 | Self::FR => "Observé à".to_string(), 48 | Self::BE => "Назірана ў".to_string(), 49 | Self::ZH => "观察时间".to_string(), 50 | Self::ES => "Observado en".to_string(), 51 | Self::PT => "Observado em".to_string(), 52 | Self::IT => "Osservato a".to_string(), 53 | Self::JA => "で観察されました".to_string(), 54 | Self::UK => "Спостерігається в".to_string(), 55 | Self::SV => "Observerat vid".to_string(), 56 | } 57 | } 58 | pub fn feels_like(&self) -> String { 59 | match &self { 60 | Self::EN => "Feels Like".to_string(), 61 | Self::DE => "Gefühlt wie".to_string(), 62 | Self::PL => "Temperatura odczuwalna".to_string(), 63 | Self::RU => "Ощущается как".to_string(), 64 | Self::TR => "Hissedilen".to_string(), 65 | Self::FR => "Ressenti".to_string(), 66 | Self::BE => "Адчуваецца як".to_string(), 67 | Self::ZH => "体感温度".to_string(), 68 | Self::ES => "Sensación térmica".to_string(), 69 | Self::PT => "Sensação térmica".to_string(), 70 | Self::IT => "Sensazione Termica".to_string(), 71 | Self::JA => "体感温度".to_string(), 72 | Self::UK => "Відчувається як".to_string(), 73 | Self::SV => "Känns som".to_string(), 74 | } 75 | } 76 | pub fn humidity(&self) -> String { 77 | match &self { 78 | Self::EN => "Humidity".to_string(), 79 | Self::DE => "Luftfeuchtigkeit".to_string(), 80 | Self::PL => "Wilgotność".to_string(), 81 | Self::RU => "Влажность".to_string(), 82 | Self::TR => "Nem".to_string(), 83 | Self::FR => "Humidité".to_string(), 84 | Self::BE => "Вільготнасць".to_string(), 85 | Self::ZH => "湿度".to_string(), 86 | Self::ES => "Humedad".to_string(), 87 | Self::PT => "Umidade".to_string(), 88 | Self::IT => "Umidità".to_string(), 89 | Self::JA => "湿度".to_string(), 90 | Self::UK => "Вогкість".to_string(), 91 | Self::SV => "Luftfuktighet".to_string(), 92 | } 93 | } 94 | pub fn location(&self) -> String { 95 | match &self { 96 | Self::EN => "Location".to_string(), 97 | Self::DE => "Standort".to_string(), 98 | Self::PL => "Lokalizacja".to_string(), 99 | Self::RU => "Местоположение".to_string(), 100 | Self::TR => "Konum".to_string(), 101 | Self::FR => "Lieu".to_string(), 102 | Self::BE => "Месцазнаходжанне".to_string(), 103 | Self::ZH => "地区".to_string(), 104 | Self::ES => "Ubicación".to_string(), 105 | Self::PT => "Localização".to_string(), 106 | Self::IT => "Posizione".to_string(), 107 | Self::JA => "地点".to_string(), 108 | Self::UK => "Розташування".to_string(), 109 | Self::SV => "Plats".to_string(), 110 | } 111 | } 112 | pub fn today(&self) -> String { 113 | match &self { 114 | Self::EN => "Today".to_string(), 115 | Self::DE => "Heute".to_string(), 116 | Self::PL => "Dzisiaj".to_string(), 117 | Self::RU => "Сегодня".to_string(), 118 | Self::TR => "Bugün".to_string(), 119 | Self::FR => "Aujourd'hui".to_string(), 120 | Self::BE => "Сёння".to_string(), 121 | Self::ZH => "今日天气".to_string(), 122 | Self::ES => "Hoy".to_string(), 123 | Self::PT => "Hoje".to_string(), 124 | Self::IT => "Oggi".to_string(), 125 | Self::JA => "今日".to_string(), 126 | Self::UK => "Сьогодні".to_string(), 127 | Self::SV => "Idag".to_string(), 128 | } 129 | } 130 | pub fn tomorrow(&self) -> String { 131 | match &self { 132 | Self::EN => "Tomorrow".to_string(), 133 | Self::DE => "Morgen".to_string(), 134 | Self::PL => "Jutro".to_string(), 135 | Self::RU => "Завтра".to_string(), 136 | Self::TR => "Yarın".to_string(), 137 | Self::FR => "Demain".to_string(), 138 | Self::BE => "Заўтра".to_string(), 139 | Self::ZH => "明日天气".to_string(), 140 | Self::ES => "Mañana".to_string(), 141 | Self::PT => "Amanhã".to_string(), 142 | Self::IT => "Domani".to_string(), 143 | Self::JA => "明日".to_string(), 144 | Self::UK => "Завтра".to_string(), 145 | Self::SV => "Imorgon".to_string(), 146 | } 147 | } 148 | pub fn fog(&self) -> String { 149 | match &self { 150 | Self::EN => "Fog".to_string(), 151 | Self::DE => "Nebel".to_string(), 152 | Self::PL => "Mgła".to_string(), 153 | Self::RU => "Туман".to_string(), 154 | Self::TR => "Sis".to_string(), 155 | Self::FR => "Brouillard".to_string(), 156 | Self::BE => "Туман".to_string(), 157 | Self::ZH => "雾".to_string(), 158 | Self::ES => "Niebla".to_string(), 159 | Self::PT => "Nevoeiro".to_string(), 160 | Self::IT => "Nebbia".to_string(), 161 | Self::JA => "霧".to_string(), 162 | Self::UK => "Туман".to_string(), 163 | Self::SV => "Dimma".to_string(), 164 | } 165 | } 166 | pub fn frost(&self) -> String { 167 | match &self { 168 | Self::EN => "Frost".to_string(), 169 | Self::DE => "Frost".to_string(), 170 | Self::PL => "Mróz".to_string(), 171 | Self::RU => "Мороз".to_string(), 172 | Self::TR => "Don".to_string(), 173 | Self::FR => "Gel".to_string(), 174 | Self::BE => "Мароз".to_string(), 175 | Self::ZH => "霜".to_string(), 176 | Self::ES => "Escarcha".to_string(), 177 | Self::PT => "Geada".to_string(), 178 | Self::IT => "Gelo".to_string(), 179 | Self::JA => "霜".to_string(), 180 | Self::UK => "Мороз".to_string(), 181 | Self::SV => "Frost".to_string(), 182 | } 183 | } 184 | pub fn overcast(&self) -> String { 185 | match &self { 186 | Self::EN => "Overcast".to_string(), 187 | Self::DE => "Bewölkung".to_string(), 188 | Self::PL => "Zachmurzenie".to_string(), 189 | Self::RU => "Пасмурно".to_string(), 190 | Self::TR => "Bulutlu".to_string(), 191 | Self::FR => "Couvert".to_string(), 192 | Self::BE => "Хмурна".to_string(), 193 | Self::ZH => "多云".to_string(), 194 | Self::ES => "Nublado".to_string(), 195 | Self::PT => "Nublado".to_string(), 196 | Self::IT => "Nuvoloso".to_string(), 197 | Self::JA => "曇り".to_string(), 198 | Self::UK => "Похмуро".to_string(), 199 | Self::SV => "Mulet".to_string(), 200 | } 201 | } 202 | pub fn rain(&self) -> String { 203 | match &self { 204 | Self::EN => "Rain".to_string(), 205 | Self::DE => "Regen".to_string(), 206 | Self::PL => "Deszcz".to_string(), 207 | Self::RU => "Дождь".to_string(), 208 | Self::TR => "Yağmur".to_string(), 209 | Self::FR => "Pluie".to_string(), 210 | Self::BE => "Дождж".to_string(), 211 | Self::ZH => "雨".to_string(), 212 | Self::ES => "Lluvia".to_string(), 213 | Self::PT => "Chuva".to_string(), 214 | Self::IT => "Pioggia".to_string(), 215 | Self::JA => "雨".to_string(), 216 | Self::UK => "Дощ".to_string(), 217 | Self::SV => "Regn".to_string(), 218 | } 219 | } 220 | pub fn snow(&self) -> String { 221 | match &self { 222 | Self::EN => "Snow".to_string(), 223 | Self::DE => "Schnee".to_string(), 224 | Self::PL => "Śnieg".to_string(), 225 | Self::RU => "Снег".to_string(), 226 | Self::TR => "Kar".to_string(), 227 | Self::FR => "Neige".to_string(), 228 | Self::BE => "Снег".to_string(), 229 | Self::ZH => "雪".to_string(), 230 | Self::ES => "Nieve".to_string(), 231 | Self::PT => "Neve".to_string(), 232 | Self::IT => "Neve".to_string(), 233 | Self::JA => "雪".to_string(), 234 | Self::UK => "Сніг".to_string(), 235 | Self::SV => "Snö".to_string(), 236 | } 237 | } 238 | pub fn sunshine(&self) -> String { 239 | match &self { 240 | Self::EN => "Sunshine".to_string(), 241 | Self::DE => "Sonnenschein".to_string(), 242 | Self::PL => "Nasłonecznienie".to_string(), 243 | Self::RU => "Солнечно".to_string(), 244 | Self::TR => "Güneş ışığı".to_string(), 245 | Self::FR => "Ensoleillé".to_string(), 246 | Self::BE => "Сонечна".to_string(), 247 | Self::ZH => "晴".to_string(), 248 | Self::ES => "Soleado".to_string(), 249 | Self::PT => "Sol".to_string(), 250 | Self::IT => "Sole".to_string(), 251 | Self::JA => "晴れ".to_string(), 252 | Self::UK => "Сонячно".to_string(), 253 | Self::SV => "Solsken".to_string(), 254 | } 255 | } 256 | pub fn thunder(&self) -> String { 257 | match &self { 258 | Self::EN => "Thunder".to_string(), 259 | Self::DE => "Donner".to_string(), 260 | Self::PL => "Burza".to_string(), 261 | Self::RU => "Гроза".to_string(), 262 | Self::TR => "Gök gürültüsü".to_string(), 263 | Self::FR => "Orages".to_string(), 264 | Self::BE => "Навальніца".to_string(), 265 | Self::ZH => "雷暴".to_string(), 266 | Self::ES => "Tormenta".to_string(), 267 | Self::PT => "Trovão".to_string(), 268 | Self::IT => "Tuono".to_string(), 269 | Self::JA => "雷".to_string(), 270 | Self::UK => "Гроза".to_string(), 271 | Self::SV => "Åska".to_string(), 272 | } 273 | } 274 | pub fn wind(&self) -> String { 275 | match &self { 276 | Self::EN => "Wind".to_string(), 277 | Self::DE => "Wind".to_string(), 278 | Self::PL => "Wiatr".to_string(), 279 | Self::RU => "Ветер".to_string(), 280 | Self::TR => "Rüzgar".to_string(), 281 | Self::FR => "Vent".to_string(), 282 | Self::BE => "Вецер".to_string(), 283 | Self::ZH => "风速".to_string(), 284 | Self::ES => "Viento".to_string(), 285 | Self::PT => "Vento".to_string(), 286 | Self::IT => "Vento".to_string(), 287 | Self::JA => "風速".to_string(), 288 | Self::UK => "Вітер".to_string(), 289 | Self::SV => "Vind".to_string(), 290 | } 291 | } 292 | pub fn weather_desc(&self) -> String { 293 | match &self { 294 | Lang::EN => "weatherDesc".to_string(), 295 | Lang::DE => "lang_de".to_string(), 296 | Lang::PL => "lang_pl".to_string(), 297 | Lang::RU => "lang_ru".to_string(), 298 | Lang::TR => "lang_tr".to_string(), 299 | Lang::FR => "lang_fr".to_string(), 300 | Lang::BE => "lang_be".to_string(), 301 | Lang::ZH => "lang_zh".to_string(), 302 | Lang::ES => "lang_es".to_string(), 303 | Lang::PT => "lang_pt".to_string(), 304 | Lang::IT => "lang_it".to_string(), 305 | Lang::JA => "lang_ja".to_string(), 306 | Lang::UK => "lang_uk".to_string(), 307 | Lang::SV => "lang_sv".to_string(), 308 | } 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use core::time; 2 | use std::collections::HashMap; 3 | use std::fs::{metadata, read_to_string, File}; 4 | use std::io::Write; 5 | use std::process::exit; 6 | use std::thread; 7 | use std::time::{Duration, SystemTime}; 8 | 9 | use chrono::prelude::*; 10 | use clap::Parser; 11 | use reqwest::blocking::Client; 12 | use serde_json::{json, Value}; 13 | 14 | use crate::cli::Args; 15 | use crate::constants::{ICON_PLACEHOLDER, WEATHER_CODES, WEATHER_CODES_NERD}; 16 | use crate::format::{format_ampm_time, format_chances, format_indicator, format_temp, format_time}; 17 | use crate::lang::Lang; 18 | 19 | mod cli; 20 | mod constants; 21 | mod format; 22 | mod lang; 23 | 24 | fn main() { 25 | let args = Args::parse(); 26 | let lang = if let Some(lang) = args.lang { 27 | lang 28 | } else { 29 | Lang::EN 30 | }; 31 | 32 | let mut data = HashMap::new(); 33 | 34 | let location = args.location.unwrap_or(String::new()); 35 | let weather_url = format!( 36 | "https://{}/{}?format=j1", 37 | lang.wttr_in_subdomain(), 38 | location 39 | ); 40 | let cachefile = format!( 41 | "/tmp/wttrbar-{}-{}.json", 42 | location, 43 | lang.wttr_in_subdomain() 44 | ); 45 | 46 | let mut iterations = 0; 47 | let threshold = 20; 48 | 49 | let is_cache_file_recent = if let Ok(metadata) = metadata(&cachefile) { 50 | let ten_minutes_ago = SystemTime::now() - Duration::from_secs(600); 51 | metadata 52 | .modified() 53 | .map_or(false, |mod_time| mod_time > ten_minutes_ago) 54 | } else { 55 | false 56 | }; 57 | 58 | let client = Client::new(); 59 | let weather = if is_cache_file_recent { 60 | let json_str = read_to_string(&cachefile).unwrap(); 61 | serde_json::from_str::(&json_str).unwrap() 62 | } else { 63 | loop { 64 | match client.get(&weather_url).send() { 65 | Ok(response) => match response.json::() { 66 | Ok(json) => break json, 67 | Err(_) => { 68 | println!("{{\"text\":\"⛓️‍💥\", \"tooltip\":\"invalid wttr.in response\"}}"); 69 | exit(0) 70 | } 71 | }, 72 | Err(_) => { 73 | iterations += 1; 74 | thread::sleep(time::Duration::from_millis(500 * iterations)); 75 | 76 | if iterations == threshold { 77 | println!("{{\"text\":\"⛓️‍💥\", \"tooltip\":\"cannot access wttr.in\"}}"); 78 | exit(0) 79 | } 80 | } 81 | } 82 | } 83 | }; 84 | 85 | if !is_cache_file_recent { 86 | let mut file = File::create(&cachefile) 87 | .expect(format!("Unable to create cache file at {}", cachefile).as_str()); 88 | 89 | file.write_all(serde_json::to_string_pretty(&weather).unwrap().as_bytes()) 90 | .expect(format!("Unable to write cache file at {}", cachefile).as_str()); 91 | } 92 | let current_condition = &weather["current_condition"][0]; 93 | let nearest_area = &weather["nearest_area"][0]; 94 | let feels_like = if args.fahrenheit { 95 | current_condition["FeelsLikeF"].as_str().unwrap() 96 | } else { 97 | current_condition["FeelsLikeC"].as_str().unwrap() 98 | }; 99 | let weather_code = current_condition["weatherCode"].as_str().unwrap(); 100 | 101 | let weather_icon = { 102 | if args.nerd { 103 | WEATHER_CODES_NERD 104 | } else { 105 | WEATHER_CODES 106 | } 107 | } 108 | .iter() 109 | .find(|(code, _)| *code == weather_code.parse::().unwrap()) 110 | .map(|(_, symbol)| symbol) 111 | .unwrap(); 112 | 113 | let text = match args.custom_indicator { 114 | None => { 115 | let main_indicator_code = if args.fahrenheit && args.main_indicator == "temp_C" { 116 | "temp_F" 117 | } else { 118 | args.main_indicator.as_str() 119 | }; 120 | let indicator = current_condition[main_indicator_code].as_str().unwrap(); 121 | if args.vertical_view { 122 | format!("{}\n{}", weather_icon, indicator) 123 | } else { 124 | format!("{} {}", weather_icon, indicator) 125 | } 126 | } 127 | Some(expression) => { 128 | format_indicator(current_condition, nearest_area, expression, weather_icon) 129 | } 130 | }; 131 | data.insert("text", text); 132 | 133 | let mut tooltip = format!( 134 | "{} {}°\n", 135 | current_condition[lang.weather_desc()][0]["value"] 136 | .as_str() 137 | .unwrap(), 138 | if args.fahrenheit { 139 | current_condition["temp_F"].as_str().unwrap() 140 | } else { 141 | current_condition["temp_C"].as_str().unwrap() 142 | }, 143 | ); 144 | tooltip += &format!("{}: {}°\n", lang.feels_like(), feels_like); 145 | if args.mph { 146 | tooltip += &format!( 147 | "{}: {} mph\n", 148 | lang.wind(), 149 | current_condition["windspeedMiles"].as_str().unwrap() 150 | ); 151 | } else { 152 | tooltip += &format!( 153 | "{}: {} km/h\n", 154 | lang.wind(), 155 | current_condition["windspeedKmph"].as_str().unwrap() 156 | ); 157 | } 158 | tooltip += &format!( 159 | "{}: {}%\n", 160 | lang.humidity(), 161 | current_condition["humidity"].as_str().unwrap() 162 | ); 163 | let nearest_area = &weather["nearest_area"][0]; 164 | tooltip += &format!( 165 | "{}: {}, {}, {}\n", 166 | lang.location(), 167 | nearest_area["areaName"][0]["value"].as_str().unwrap(), 168 | nearest_area["region"][0]["value"].as_str().unwrap(), 169 | nearest_area["country"][0]["value"].as_str().unwrap() 170 | ); 171 | 172 | if args.observation_time { 173 | if let Some(obs_time) = current_condition["observation_time"].as_str() { 174 | if let Ok(time) = NaiveTime::parse_from_str(obs_time, "%I:%M %p") { 175 | let formatted_time = if args.ampm { 176 | obs_time.to_string() 177 | } else { 178 | time.format("%H:%M").to_string() 179 | }; 180 | tooltip += &format!("{}: {}\n", lang.observation_time(), formatted_time); 181 | } 182 | } 183 | } 184 | 185 | let now = Local::now(); 186 | 187 | let today = Local::now().date_naive(); 188 | let mut forecast = weather["weather"].as_array().unwrap().clone(); 189 | forecast.retain(|item| { 190 | let item_date = 191 | NaiveDate::parse_from_str(item["date"].as_str().unwrap(), "%Y-%m-%d").unwrap(); 192 | item_date >= today 193 | }); 194 | 195 | for (i, day) in forecast.iter().enumerate() { 196 | tooltip += "\n"; 197 | if i == 0 { 198 | tooltip += &format!("{}, ", lang.today()); 199 | } 200 | if i == 1 { 201 | tooltip += &format!("{}, ", lang.tomorrow()); 202 | } 203 | let date = NaiveDate::parse_from_str(day["date"].as_str().unwrap(), "%Y-%m-%d").unwrap(); 204 | tooltip += &format!("{}\n", date.format(args.date_format.as_str())); 205 | 206 | let (max_temp, min_temp) = if args.fahrenheit { 207 | ( 208 | day["maxtempF"].as_str().unwrap(), 209 | day["mintempF"].as_str().unwrap(), 210 | ) 211 | } else { 212 | ( 213 | day["maxtempC"].as_str().unwrap(), 214 | day["mintempC"].as_str().unwrap(), 215 | ) 216 | }; 217 | 218 | tooltip += &format!( 219 | "{} {}° {} {}° ", 220 | if args.nerd { "󰳡" } else { "⬆️" }, 221 | max_temp, 222 | if args.nerd { "󰳛" } else { "⬇️" }, 223 | min_temp 224 | ); 225 | 226 | tooltip += &format!( 227 | "{} {} {} {}\n", 228 | if args.nerd { "󰖜" } else { "🌅" }, 229 | format_ampm_time(day, "sunrise", args.ampm), 230 | if args.nerd { "󰖛" } else { "🌇" }, 231 | format_ampm_time(day, "sunset", args.ampm) 232 | ); 233 | 234 | for hour in day["hourly"].as_array().unwrap() { 235 | let hour_time = hour["time"].as_str().unwrap(); 236 | let formatted_hour_time = if hour_time.len() >= 2 { 237 | hour_time[..hour_time.len() - 2].to_string() 238 | } else { 239 | hour_time.to_string() 240 | }; 241 | if i == 0 242 | && now.hour() >= 2 243 | && formatted_hour_time.parse::().unwrap() < now.hour() - 2 244 | { 245 | continue; 246 | } 247 | 248 | let mut tooltip_line = format!( 249 | "{} {} {} {}", 250 | format_time(hour["time"].as_str().unwrap(), args.ampm), 251 | if args.nerd { 252 | WEATHER_CODES_NERD 253 | } else { 254 | WEATHER_CODES 255 | } 256 | .iter() 257 | .find(|(code, _)| *code 258 | == hour["weatherCode"] 259 | .as_str() 260 | .unwrap() 261 | .parse::() 262 | .unwrap()) 263 | .map(|(_, symbol)| symbol) 264 | .unwrap(), 265 | if args.fahrenheit { 266 | format_temp(hour["FeelsLikeF"].as_str().unwrap()) 267 | } else { 268 | format_temp(hour["FeelsLikeC"].as_str().unwrap()) 269 | }, 270 | hour[lang.weather_desc()][0]["value"].as_str().unwrap(), 271 | ); 272 | if !args.hide_conditions { 273 | tooltip_line += format!(", {}", format_chances(hour, &lang)).as_str(); 274 | } 275 | tooltip_line += "\n"; 276 | tooltip += &tooltip_line; 277 | } 278 | } 279 | data.insert("tooltip", tooltip); 280 | 281 | let css_class = current_condition[lang.weather_desc()][0]["value"] 282 | .as_str() 283 | .unwrap() 284 | .to_lowercase() 285 | .split(',') 286 | .next() 287 | .map(|s| s.trim().replace(' ', "_")) 288 | .unwrap_or_default(); 289 | data.insert("class", css_class); 290 | 291 | let json_data = json!(data); 292 | println!("{}", json_data); 293 | } 294 | --------------------------------------------------------------------------------