commit 3963fc50c35b9f36d73b6b8bf0d8d83d5c3f00da Author: Jana Dönszelmann Date: Fri Feb 20 12:40:07 2026 +0100 set envvars diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..b369423 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1907 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "atomic" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "clap" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "clap_lex" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "compact_str" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags 2.11.0", + "crossterm_winapi", + "derive_more", + "document-features", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "csscolorparser" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" +dependencies = [ + "lab", + "phf", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "deltae" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" + +[[package]] +name = "deranged" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.117", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "euclid" +version = "0.22.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df61bf483e837f88d5c2291dcf55c67be7e676b3a51acc48db3a7b163b91ed63" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fancy-regex" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +dependencies = [ + "bit-set", + "regex", +] + +[[package]] +name = "filedescriptor" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" +dependencies = [ + "libc", + "thiserror 1.0.69", + "winapi", +] + +[[package]] +name = "finl_unicode" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "instability" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357b7205c6cd18dd2c86ed312d1e70add149aea98e7ef72b9fdf0270e555c11d" +dependencies = [ + "darling", + "indoc", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jiff" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c867c356cc096b33f4981825ab281ecba3db0acefe60329f044c1789d94c6543" +dependencies = [ + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-sys", +] + +[[package]] +name = "jiff-static" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7946b4325269738f270bb55b3c19ab5c5040525f83fd625259422a9d25d9be5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68971ebff725b9e2ca27a601c5eb38a4c5d64422c4cbab0c535f248087eda5c2" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "kasuari" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fe90c1150662e858c7d5f945089b7517b0a80d8bf7ba4b1b5ffc984e7230a5b" +dependencies = [ + "hashbrown 0.16.1", + "portable-atomic", + "thiserror 2.0.18", +] + +[[package]] +name = "lab" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "line-clipping" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4de44e98ddbf09375cbf4d17714d18f39195f4f4894e8524501726fd9a8a4a" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "mac_address" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" +dependencies = [ + "nix", + "winapi", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memmem" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pest" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "pest_meta" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "ratatui" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc" +dependencies = [ + "instability", + "ratatui-core", + "ratatui-crossterm", + "ratatui-macros", + "ratatui-termwiz", + "ratatui-widgets", +] + +[[package]] +name = "ratatui-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" +dependencies = [ + "bitflags 2.11.0", + "compact_str", + "hashbrown 0.16.1", + "indoc", + "itertools", + "kasuari", + "lru", + "strum", + "thiserror 2.0.18", + "unicode-segmentation", + "unicode-truncate", + "unicode-width", +] + +[[package]] +name = "ratatui-crossterm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3" +dependencies = [ + "cfg-if", + "crossterm", + "instability", + "ratatui-core", +] + +[[package]] +name = "ratatui-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f1342a13e83e4bb9d0b793d0ea762be633f9582048c892ae9041ef39c936f4" +dependencies = [ + "ratatui-core", + "ratatui-widgets", +] + +[[package]] +name = "ratatui-termwiz" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f76fe0bd0ed4295f0321b1676732e2454024c15a35d01904ddb315afd3d545c" +dependencies = [ + "ratatui-core", + "termwiz", +] + +[[package]] +name = "ratatui-themes" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1a71b2897e290f5e18ba55138074b4ceb1a765ba0d7219e55fef5459497d5f" +dependencies = [ + "ratatui", + "serde", +] + +[[package]] +name = "ratatui-widgets" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.16.1", + "indoc", + "instability", + "itertools", + "line-clipping", + "ratatui-core", + "strum", + "time", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" + +[[package]] +name = "rustc-logviz" +version = "0.1.0" +dependencies = [ + "clap", + "jiff", + "ratatui", + "ratatui-themes", + "serde", + "serde_json", + "thiserror 2.0.18", + "tui-widget-list", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "terminfo" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" +dependencies = [ + "fnv", + "nom", + "phf", + "phf_codegen", +] + +[[package]] +name = "termios" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" +dependencies = [ + "libc", +] + +[[package]] +name = "termwiz" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" +dependencies = [ + "anyhow", + "base64", + "bitflags 2.11.0", + "fancy-regex", + "filedescriptor", + "finl_unicode", + "fixedbitset", + "hex", + "lazy_static", + "libc", + "log", + "memmem", + "nix", + "num-derive", + "num-traits", + "ordered-float", + "pest", + "pest_derive", + "phf", + "sha2", + "signal-hook", + "siphasher", + "terminfo", + "termios", + "thiserror 1.0.69", + "ucd-trie", + "unicode-segmentation", + "vtparse", + "wezterm-bidi", + "wezterm-blob-leases", + "wezterm-color-types", + "wezterm-dynamic", + "wezterm-input-types", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde_core", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "tui-widget-list" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81a82508e870aa543ffbd7a1720fff6dab4f985f629261de8db7502a462959d3" +dependencies = [ + "ratatui-core", + "ratatui-widgets", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-truncate" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" +dependencies = [ + "itertools", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" +dependencies = [ + "atomic", + "getrandom 0.4.1", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vtparse" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "wezterm-bidi" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec" +dependencies = [ + "log", + "wezterm-dynamic", +] + +[[package]] +name = "wezterm-blob-leases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7" +dependencies = [ + "getrandom 0.3.4", + "mac_address", + "sha2", + "thiserror 1.0.69", + "uuid", +] + +[[package]] +name = "wezterm-color-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296" +dependencies = [ + "csscolorparser", + "deltae", + "lazy_static", + "wezterm-dynamic", +] + +[[package]] +name = "wezterm-dynamic" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac" +dependencies = [ + "log", + "ordered-float", + "strsim", + "thiserror 1.0.69", + "wezterm-dynamic-derive", +] + +[[package]] +name = "wezterm-dynamic-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "wezterm-input-types" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e" +dependencies = [ + "bitflags 1.3.2", + "euclid", + "lazy_static", + "serde", + "wezterm-dynamic", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7d4a08c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "rustc-logviz" +version = "0.1.0" +edition = "2024" + +[[bin]] +name = "lv" +path = "./src/main.rs" + +[dependencies] +clap = {version="4.5", features=["derive"]} +jiff = {version = "0.2", features = ["serde"]} +ratatui = {version = "0.30.0", features=["unstable-rendered-line-info"]} +ratatui-themes = { version = "0.2", features = ["serde"] } +tui-widget-list = "0.15" +serde = {version = "1", features = ["derive"]} +serde_json = "1" +thiserror = "2" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..84baf98 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,107 @@ +use std::{ + env::temp_dir, + ffi::OsString, + fs::{self, File}, + path::PathBuf, + process::{Command, exit}, +}; + +mod tui; + +use clap::{Parser, Subcommand}; +use jiff::Zoned; + +#[derive(Subcommand, Debug)] +enum Preset { + /// Explore logs + Show, + + /// Get all the typesystem related logs + Types, + /// Get all logs + All, + Crates { + #[arg(short, long)] + crates: Vec, + }, +} + +fn default_tempdir() -> PathBuf { + temp_dir().join("rustc-logviz") +} + +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +struct Args { + #[command(subcommand)] + preset: Preset, + + #[arg(default_value_os_t = default_tempdir())] + #[arg(global = true)] + #[arg(long = "logs-dir")] + logs_dir: PathBuf, + + #[arg(trailing_var_arg = true)] + #[arg(allow_hyphen_values = true)] + #[arg(global = true)] + rest: Vec, +} + +fn main() { + let Args { + preset, + logs_dir, + rest, + } = Args::parse(); + + let rustc_log = match preset { + Preset::Show => { + tui::run(logs_dir); + exit(0); + } + Preset::Types => { + "rustc_hir_typeck,rustc_infer,rustc_next_trait_solver,rustc_middle,rustc_traits,rustc_trait_selection,rustc_type_ir,rustc_ty_utils".to_string() + } + Preset::All => "debug".to_string(), + Preset::Crates { crates } => format!("{}", crates.join(",")), + }; + + let (first, rest) = { + let mut rest = rest.into_iter(); + let Some(first) = rest.next() else { + eprintln!("no command given, exiting"); + exit(0); + }; + (first, rest.collect::>()) + }; + + if let Err(e) = fs::create_dir_all(&logs_dir) { + eprintln!("failed to create logs dir at {}: {e:?}", logs_dir.display()); + exit(1) + } + + let now = Zoned::now().strftime("%b %e %H:%M:%S"); + let log_file_path = logs_dir.join(format!("{now}.log")); + let log_file = match File::create(&log_file_path) { + Ok(i) => i, + Err(e) => { + eprintln!( + "failed to create logfile at {}: {e:?}", + log_file_path.display() + ); + exit(1) + } + }; + + eprintln!("outputting json logs to {}", log_file_path.display()); + if let Err(e) = Command::new(first) + .args(rest) + .env("RUSTC_LOG", rustc_log) + .env("RUSTC_LOG_FORMAT_JSON", "1") + .env("RUSTC_LOG_OUTPUT_TARGET", log_file_path) + .status() + { + eprintln!("failed to spawn command: {e:?}, exiting"); + exit(1); + } +} diff --git a/src/tui/filter.rs b/src/tui/filter.rs new file mode 100644 index 0000000..e3ec3e9 --- /dev/null +++ b/src/tui/filter.rs @@ -0,0 +1,158 @@ +use crate::tui::model::LogEntry; + +pub enum WipMatcher { + Field { + name: Option, + value: Option, + }, + Specific { + path: Option>, + }, +} + +impl WipMatcher { + fn validate(&self) -> Option { + match self { + WipMatcher::Field { + name: Some(name), + value: Some(value), + } => Some(Matcher::Field { + name: name.clone(), + value: value.clone(), + }), + WipMatcher::Specific { path: Some(path) } => { + Some(Matcher::Specific { path: path.clone() }) + } + _ => None, + } + } +} + +pub enum Matcher { + Field { + name: String, + value: serde_json::Value, + }, + Specific { + path: Vec, + }, +} + +impl Matcher { + pub fn matches(&self, entry: &LogEntry) -> bool { + match self { + Matcher::Field { name, value } => entry + .all_fields() + .fields + .get(name) + .is_some_and(|v| v == value), + Matcher::Specific { path } => false, + } + } +} + +#[derive(Clone)] +pub enum FilterKind { + Inline, + Remove, +} + +pub struct Filter { + pub matcher: Matcher, + pub kind: FilterKind, +} + +pub struct WipFilter { + pub matcher: Option, + pub kind: Option, + pub selection: FilterSelection, +} + +impl WipFilter { + pub fn validate(&self) -> Option { + let Self { + matcher, + kind, + selection: _, + } = self; + let Some(matcher) = matcher else { return None }; + Some(Filter { + matcher: matcher.validate()?, + kind: kind.clone()?, + }) + } + + pub fn clear(&mut self) { + match self.selection { + FilterSelection::Kind => self.kind = None, + FilterSelection::MatcherKind => {} + FilterSelection::Matcher => {} + FilterSelection::Confirm => {} + } + } + + pub fn right(&mut self) { + match self.selection { + FilterSelection::Kind => { + self.kind = Some(match self.kind { + None => FilterKind::Inline, + Some(FilterKind::Inline) => FilterKind::Remove, + Some(FilterKind::Remove) => FilterKind::Inline, + }) + } + FilterSelection::MatcherKind => {} + FilterSelection::Matcher => {} + FilterSelection::Confirm => {} + } + } + pub fn left(&mut self) { + match self.selection { + FilterSelection::Kind => { + self.kind = Some(match self.kind { + None => FilterKind::Remove, + Some(FilterKind::Remove) => FilterKind::Inline, + Some(FilterKind::Inline) => FilterKind::Inline, + }) + } + FilterSelection::MatcherKind => {} + FilterSelection::Matcher => {} + FilterSelection::Confirm => {} + } + } +} + +#[derive(Clone, Copy)] +pub enum FilterSelection { + Kind, + MatcherKind, + Matcher, + Confirm, +} + +impl FilterSelection { + pub fn next(&mut self) { + *self = match *self { + Self::Kind => Self::MatcherKind, + Self::MatcherKind => Self::Matcher, + Self::Matcher => Self::Confirm, + Self::Confirm => Self::Confirm, + }; + } + + pub fn prev(&mut self) { + *self = match self { + Self::Kind => Self::Kind, + Self::MatcherKind => Self::Kind, + Self::Matcher => Self::MatcherKind, + Self::Confirm => Self::Matcher, + }; + } +} + +#[derive(Clone, Copy)] +pub enum FieldMatcherSelection { + Field, + Value, +} + +impl FieldMatcherSelection {} diff --git a/src/tui/log_viewer.rs b/src/tui/log_viewer.rs new file mode 100644 index 0000000..418b8a8 --- /dev/null +++ b/src/tui/log_viewer.rs @@ -0,0 +1,166 @@ +use std::{collections::HashMap, mem, rc::Rc}; + +use crate::tui::{ + model::LogEntry, + reader::{FilterAdapter, LogfileReader}, +}; +use tui_widget_list::ListState; + +#[derive(Default, Clone)] +pub struct LogView { + first_item: usize, + selected: usize, +} + +pub struct LogViewer { + stack: Vec, + curr: LogView, + cache: HashMap, LogView>, + + pub last_height: usize, + pub footer_selected: bool, + pub footer_list: ListState, +} + +impl LogViewer { + pub fn new() -> Self { + Self { + stack: Vec::new(), + curr: LogView::default(), + cache: HashMap::new(), + footer_list: ListState::default(), + footer_selected: false, + last_height: 0, + } + } + + pub fn update_num_items(&mut self, num_visible_items: usize) { + while self.curr.selected >= self.curr.first_item + num_visible_items { + self.curr.first_item += 1; + } + self.last_height = num_visible_items; + } + + pub fn footer_fields(&self, file: &mut LogfileReader) -> Vec<(String, serde_json::Value)> { + if let Some(selected) = self.selected(file) { + selected.all_fields().fields.into_iter().collect::>() + } else { + Vec::new() + } + } + + pub fn items( + &self, + file: &mut LogfileReader, + max: usize, + ) -> Option<(Vec>, usize, usize)> { + let items: Vec<_> = if self.stack.is_empty() { + file.iter_from(self.curr.first_item).take(max).collect() + } else { + let mut stack = self.stack.iter(); + let first = stack.next().unwrap(); + let mut curr_log_entry = file.iter_from(first.selected).next()?; + for elem in stack { + curr_log_entry = curr_log_entry.get(elem.selected)?; + } + + match curr_log_entry.as_ref() { + LogEntry::Single { .. } => return None, + LogEntry::Sub { sub_entries, .. } => { + FilterAdapter::new(file, &sub_entries[self.curr.first_item..]) + .take(max) + .cloned() + .collect() + } + } + }; + + Some((items, self.curr.first_item, self.curr.selected)) + } + + pub fn selected(&self, file: &mut LogfileReader) -> Option> { + self.items(file, self.curr.selected - self.curr.first_item + 1)? + .0 + .get(self.curr.selected - self.curr.first_item) + .cloned() + } + + fn update_footer_select(&mut self) { + self.footer_list.select(Some(0)); + } + + pub fn prev(&mut self) { + if self.footer_selected { + self.footer_list.previous(); + } else { + self.curr.selected = self.curr.selected.saturating_sub(1); + self.curr.first_item = self.curr.first_item.min(self.curr.selected); + self.update_footer_select(); + } + } + + pub fn next(&mut self) { + if self.footer_selected { + self.footer_list.next(); + } else { + self.curr.selected += 1; + self.update_footer_select(); + } + } + + pub fn page_down(&mut self) { + self.curr.selected += self.last_height; + self.footer_selected = false; + self.update_footer_select(); + } + + pub fn page_up(&mut self) { + self.curr.selected = self.curr.selected.saturating_sub(self.last_height); + self.curr.first_item = self.curr.first_item.min(self.curr.selected); + self.footer_selected = false; + self.update_footer_select(); + } + + pub fn home(&mut self) { + if self.footer_selected { + self.footer_list.select(Some(0)); + } else { + self.curr.selected = 0; + self.curr.first_item = 0; + self.update_footer_select(); + } + } + + pub fn path(&self) -> Vec { + self.stack.iter().map(|i| i.selected).collect() + } + + pub fn back(&mut self) { + self.cache.insert(self.path(), self.curr); + self.curr = self.stack.pop().unwrap_or_default(); + self.footer_selected = false; + self.update_footer_select(); + } + + pub fn switch_focus(&mut self) { + self.footer_selected = !self.footer_selected; + } + + pub fn enter(&mut self, file: &mut LogfileReader) { + if !self.footer_selected { + let Some(s) = self.selected(file) else { + return; + }; + if let LogEntry::Single { .. } = s.as_ref() { + return; + } + + self.stack + .push(mem::replace(&mut self.curr, LogView::default())); + if let Some(cached_view) = self.cache.get(&self.path()) { + self.curr = *cached_view; + } + self.update_footer_select(); + } + } +} diff --git a/src/tui/mod.rs b/src/tui/mod.rs new file mode 100644 index 0000000..6df472d --- /dev/null +++ b/src/tui/mod.rs @@ -0,0 +1,594 @@ +use ratatui_themes::{Theme, ThemeName}; +use std::{ + fs::{self, DirEntry}, + io, + path::{Path, PathBuf}, + process::exit, +}; +use tui_widget_list::{ListBuilder, ListView}; + +use crate::tui::{ + filter::{FilterKind, WipMatcher}, + log_viewer::LogViewer, +}; +use crate::tui::{ + filter::{FilterSelection, WipFilter}, + reader::LogfileReader, +}; +use ratatui::{ + DefaultTerminal, + buffer::Buffer, + crossterm::event::{self, Event, KeyCode, KeyModifiers}, + layout::{Constraint, HorizontalAlignment, Layout, Rect}, + style::Style, + widgets::{ + Block, Clear, List, ListItem, ListState, Padding, Paragraph, StatefulWidget, Widget, Wrap, + }, +}; + +pub mod filter; +pub mod log_viewer; +pub mod model; +pub mod reader; + +pub fn run(logs_dir: PathBuf) { + let terminal = ratatui::init(); + let theme = Theme::new(ThemeName::OneDarkPro); + let app_result = App::new(logs_dir, theme).run(terminal); + ratatui::restore(); + + if let Err(e) = app_result { + eprintln!("error in tui: {e:?}"); + exit(1); + } +} + +enum Tab { + FileChooser { + files: Vec, + state: ListState, + last_height: usize, + }, + LogViewer(LogViewer), + CreateFilter { + filter: WipFilter, + }, + Empty, +} + +impl Tab { + fn name(&self, path: Option<&Path>) -> String { + match (self, path) { + (Tab::Empty, _) => "dummy".to_string(), + (Tab::FileChooser { .. }, _) => "choose a file".to_string(), + (Tab::LogViewer(_), Some(path)) => format!("logs of {}", path.display()), + (Tab::LogViewer(_), None) => "logs".to_string(), + (Tab::CreateFilter { .. }, _) => "create filter".to_string(), + } + } +} + +fn initialize_filter( + lv: &mut LogViewer, + file: &mut LogfileReader, + kind: Option, +) -> WipFilter { + let matcher = if lv.footer_selected { + let footer_fields = lv.footer_fields(file); + let (key, value) = footer_fields + .get(lv.footer_list.selected.unwrap_or(0)) + .map_or((None, None), |(k, v)| (Some(k), Some(v))); + Some(WipMatcher::Field { + name: key.cloned(), + value: value.cloned(), + }) + } else { + Some(WipMatcher::Specific { + path: Some(lv.path().clone()), + }) + }; + + WipFilter { + matcher, + kind, + selection: filter::FilterSelection::Kind, + } +} + +struct App { + tabs: Vec, + logs_dir: PathBuf, + current_file: Option, + theme: Theme, +} + +impl App { + fn new(logs_dir: PathBuf, theme: Theme) -> Self { + let mut res = Self { + tabs: Vec::new(), + current_file: None, + logs_dir, + theme, + }; + res.replace_tab(res.choose_file()); + res + } + + fn current_file_path(&self) -> Option { + self.current_file.as_ref().map(|i| i.path.to_path_buf()) + } + + fn replace_tab(&mut self, tab: Tab) { + if let Some(last) = self.tabs.last_mut() { + *last = tab; + } else { + self.tabs = vec![tab]; + } + } + + fn push_tab(&mut self, tab: Tab) { + self.tabs.push(tab); + } + + fn pop_tab(&mut self) { + let _ = self.tabs.pop(); + } + + fn choose_file(&self) -> Tab { + fn init(logs_dir: &Path) -> io::Result> { + let mut files = Vec::new(); + + for file in fs::read_dir(logs_dir)? { + let file = file?; + files.push(file); + } + + Ok(files) + } + + match init(&self.logs_dir) { + Ok(files) => Tab::FileChooser { + files, + state: ListState::default(), + last_height: 0, + }, + Err(_) => Tab::Empty, + } + } + + fn current_tab(&mut self) -> &mut Tab { + if self.tabs.is_empty() { + self.tabs.push(Tab::Empty); + } + + self.tabs.last_mut().unwrap() + } + + fn run(mut self, mut terminal: DefaultTerminal) -> io::Result<()> { + loop { + terminal.draw(|frame| frame.render_widget(&mut self, frame.area()))?; + + if let Event::Key(key) = event::read()? { + // to initialize, but we then do get manually for borrow reasons + self.current_tab(); + let num_tabs = self.tabs.len(); + match (key.code, self.tabs.last_mut().unwrap()) { + (KeyCode::Char('q'), _) => return Ok(()), + (KeyCode::Char('c'), _) if key.modifiers.contains(KeyModifiers::CONTROL) => { + return Ok(()); + } + (KeyCode::Esc, _) if num_tabs > 1 => { + self.pop_tab(); + } + (KeyCode::Char('j') | KeyCode::Down, tab) => match tab { + Tab::FileChooser { state, .. } => state.select_next(), + Tab::LogViewer(lv) => lv.next(), + Tab::Empty => {} + Tab::CreateFilter { filter } => { + filter.selection.next(); + } + }, + (KeyCode::Char('k') | KeyCode::Up, tab) => match tab { + Tab::FileChooser { state, .. } => state.select_previous(), + Tab::LogViewer(lv) => { + lv.prev(); + } + Tab::Empty => {} + Tab::CreateFilter { filter } => { + filter.selection.prev(); + } + }, + (KeyCode::PageDown, tab) => match tab { + Tab::FileChooser { + state, last_height, .. + } => state.scroll_down_by(*last_height as u16), + Tab::LogViewer(lv) => { + lv.page_down(); + } + Tab::Empty => {} + Tab::CreateFilter { .. } => {} + }, + (KeyCode::PageUp, tab) => match tab { + Tab::FileChooser { + state, last_height, .. + } => state.scroll_up_by(*last_height as u16), + Tab::LogViewer(lv) => { + lv.page_up(); + } + Tab::Empty => {} + Tab::CreateFilter { .. } => {} + }, + (KeyCode::Char('G') | KeyCode::Home, tab) => match tab { + Tab::FileChooser { state, .. } => state.select_first(), + Tab::LogViewer(lv) => { + lv.home(); + } + Tab::Empty => {} + Tab::CreateFilter { .. } => {} + }, + (KeyCode::Char('g') | KeyCode::End, tab) => match tab { + Tab::FileChooser { state, .. } => state.select_last(), + Tab::LogViewer(lv) => {} + Tab::Empty => {} + Tab::CreateFilter { .. } => {} + }, + (KeyCode::Backspace | KeyCode::Left | KeyCode::Esc, Tab::LogViewer(lv)) => { + lv.back(); + } + (KeyCode::Backspace, Tab::CreateFilter { filter }) => { + filter.clear(); + } + (KeyCode::Right, Tab::CreateFilter { filter }) => { + filter.right(); + } + (KeyCode::Left, Tab::CreateFilter { filter }) => { + filter.left(); + } + (KeyCode::Right, Tab::LogViewer(lv)) => { + if let Some(file) = &mut self.current_file { + lv.enter(file) + } + } + (KeyCode::Tab, Tab::LogViewer(lv)) => { + lv.switch_focus(); + } + (KeyCode::Char('r'), Tab::LogViewer(lv)) => { + if let Some(file) = &mut self.current_file { + let filter = initialize_filter(lv, file, Some(FilterKind::Remove)); + self.push_tab(Tab::CreateFilter { filter }); + } + } + (KeyCode::Char('i'), Tab::LogViewer(lv)) => { + if let Some(file) = &mut self.current_file { + let filter = initialize_filter(lv, file, Some(FilterKind::Inline)); + self.push_tab(Tab::CreateFilter { filter }); + } + } + (KeyCode::Enter, tab) => match tab { + Tab::FileChooser { files, state, .. } => { + if let Some(selected) = state.selected() + && let Some(selected) = files.get(selected) + { + match LogfileReader::new(&selected.path()) { + Ok(i) => { + self.current_file = Some(i); + self.replace_tab(Tab::LogViewer(LogViewer::new())); + } + Err(e) => { + panic!() + } + } + } + } + Tab::LogViewer(lv) => { + if let Some(file) = &mut self.current_file { + if lv.footer_selected { + let filter = initialize_filter(lv, file, None); + self.push_tab(Tab::CreateFilter { filter }); + } else { + lv.enter(file) + } + } + } + Tab::Empty => {} + Tab::CreateFilter { filter } => { + if let FilterSelection::Confirm = filter.selection + && let Some(file) = &mut self.current_file + { + if let Some(filter) = filter.validate() { + file.add_filter(filter); + self.pop_tab(); + + if let Tab::LogViewer(lv) = self.current_tab() { + lv.footer_selected = false; + } + } + } else { + filter.selection.next(); + } + } + }, + _ => {} + } + } + } + } +} + +impl Widget for &mut App { + fn render(self, area: Rect, buf: &mut Buffer) + where + Self: Sized, + { + let palette = self.theme.palette(); + let default = Style::new().fg(palette.fg).bg(palette.bg); + let highlighted = Style::new().fg(palette.accent).bg(palette.selection); + let border = Style::new().fg(palette.fg).bg(palette.bg); + let border_selected = Style::new().fg(palette.secondary).bg(palette.bg); + + let [header_area, main_area, footer_area] = Layout::vertical([ + Constraint::Length(2), + Constraint::Fill(1), + Constraint::Ratio(1, 4), + ]) + .areas(area); + + let [_, popup_area, _] = Layout::vertical([ + Constraint::Fill(1), + Constraint::Min(40), + Constraint::Fill(1), + ]) + .areas(area); + let [_, popup_area, _] = Layout::horizontal([ + Constraint::Fill(1), + Constraint::Min(40), + Constraint::Fill(1), + ]) + .areas(popup_area); + + let (footer_focused, header_focused) = match self.current_tab() { + Tab::FileChooser { .. } => (false, true), + Tab::LogViewer(lv) => (lv.footer_selected, !lv.footer_selected), + Tab::Empty => (false, false), + Tab::CreateFilter { .. } => (false, false), + }; + + let main_area = { + let block = Block::bordered() + .style(default) + .border_style(if header_focused { + border_selected + } else { + border + }); + let inner = block.inner(main_area); + block.render(main_area, buf); + inner + }; + + let footer_area = { + let block = Block::bordered() + .style(default) + .border_style(if footer_focused { + border_selected + } else { + border + }); + let inner = block.inner(footer_area); + block.render(footer_area, buf); + inner + }; + + let [left, middle, right] = Layout::horizontal([ + Constraint::Ratio(1, 3), + Constraint::Ratio(1, 3), + Constraint::Fill(1), + ]) + .areas(header_area); + + let current_file_path = self.current_file_path(); + let breadcrumbs = self.tabs[..self.tabs.len() - 1] + .iter() + .map(|i| i.name(current_file_path.as_deref())) + .collect::>() + .join("►"); + Paragraph::new(breadcrumbs) + .wrap(Wrap { trim: false }) + .render(left, buf); + + Paragraph::new(self.current_tab().name(current_file_path.as_deref())) + .alignment(HorizontalAlignment::Center) + .wrap(Wrap { trim: false }) + .render(middle, buf); + + for tab in &mut self.tabs { + match tab { + Tab::FileChooser { + files, + state, + last_height, + } => { + let list = List::new(files.iter().map(|file| { + ListItem::new(file.file_name().to_string_lossy().into_owned()) + })) + .style(default) + .highlight_style(highlighted); + + *last_height = main_area.height as usize; + + StatefulWidget::render(list, main_area, buf, state); + } + Tab::LogViewer(lv) => { + let Some(file) = &mut self.current_file else { + continue; + }; + + lv.update_num_items(main_area.height as usize); + + let (items, start, selected) = lv + .items(file, main_area.height as usize) + .unwrap_or_else(|| (Vec::new(), 0, 0)); + + let list = List::new(items.into_iter().enumerate().map(|(idx, i)| { + let line = i.line_text(false); + + let mut list_item = ListItem::new(line); + + if idx + start == selected { + list_item = list_item.style(highlighted); + } + + list_item + })); + Widget::render(list, main_area, buf); + + let items = lv.footer_fields(file); + let width = 20; + let builder = ListBuilder::new(|cx| { + let Some((k, v)) = &items.get(cx.index) else { + return (Paragraph::new(""), 1); + }; + let contents = serde_json::to_string_pretty(&v) + .unwrap_or(String::new()) + .replace("\n", "\n{:width$}"); + + let mut res = Paragraph::new(format!("{k:width$} {contents}")) + .wrap(Wrap { trim: false }); + + if cx.is_selected { + res = res.style(highlighted); + } + + let height = res.line_count(footer_area.width) as u16; + (res, height) + }); + + let list = ListView::new(builder, items.len()); + StatefulWidget::render(list, footer_area, buf, &mut lv.footer_list); + } + Tab::Empty => {} + Tab::CreateFilter { filter } => { + Clear.render(popup_area, buf); + let popup_area = { + let block = Block::bordered() + .title_top("create filter") + .style(default) + .padding(Padding::symmetric(3, 1)) + .border_style(border_selected); + let inner = block.inner(popup_area); + block.render(popup_area, buf); + inner + }; + + let [kind, matcher_kind, matcher_area, confirm] = Layout::vertical([ + Constraint::Length(5), + Constraint::Length(5), + Constraint::Fill(1), + Constraint::Length(1), + ]) + .areas(popup_area); + + let text = match &filter.kind { + None => "", + Some(FilterKind::Inline) => "inline items", + Some(FilterKind::Remove) => "remove item and sub-items", + }; + Paragraph::new(format!("⏴ {text} ⏵")) + .centered() + .style( + if matches!(filter.selection, filter::FilterSelection::Kind) { + highlighted + } else { + default + }, + ) + .block( + Block::bordered() + .title_top("transformation") + .padding(Padding::uniform(1)), + ) + .render(kind, buf); + + let text = match filter.matcher.as_ref() { + None => "", + Some(WipMatcher::Field { .. }) => "all logs where field matches", + Some(WipMatcher::Specific { .. }) => "this specific log", + }; + Paragraph::new(format!("⏴ {text} ⏵")) + .centered() + .style( + if matches!(filter.selection, filter::FilterSelection::MatcherKind) { + highlighted + } else { + default + }, + ) + .block( + Block::bordered() + .title_top("matcher") + .padding(Padding::uniform(1)), + ) + .render(matcher_kind, buf); + + match &filter.matcher { + Some(WipMatcher::Field { name, value }) => { + let [field_area, value_area, _] = Layout::vertical([ + Constraint::Length(5), + Constraint::Length(5), + Constraint::Fill(1), + ]) + .areas(matcher_area); + + Paragraph::new(format!("{}", name.clone().unwrap_or_default())) + .centered() + .style( + if matches!(filter.selection, filter::FilterSelection::Matcher) + { + highlighted + } else { + default + }, + ) + .block( + Block::bordered() + .title_top("field name") + .padding(Padding::uniform(1)), + ) + .render(field_area, buf); + + Paragraph::new(format!("{}", value.clone().unwrap_or_default())) + .centered() + .style( + if matches!(filter.selection, filter::FilterSelection::Matcher) + { + highlighted + } else { + default + }, + ) + .block( + Block::bordered() + .title_top("value") + .padding(Padding::uniform(1)), + ) + .render(value_area, buf); + } + Some(WipMatcher::Specific { .. }) => {} + None => {} + } + + Paragraph::new("confirm") + .centered() + .style( + if matches!(filter.selection, filter::FilterSelection::Confirm) { + highlighted + } else { + default + }, + ) + .render(confirm, buf); + } + } + } + } +} diff --git a/src/tui/model.rs b/src/tui/model.rs new file mode 100644 index 0000000..f4fdd84 --- /dev/null +++ b/src/tui/model.rs @@ -0,0 +1,134 @@ +use std::{collections::BTreeMap, rc::Rc, sync::OnceLock}; + +use jiff::Timestamp; +use ratatui::text::Line; +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +pub enum Level { + #[serde(rename = "TRACE")] + Trace, + #[serde(rename = "DEBUG")] + Debug, + #[serde(rename = "INFO")] + Info, + #[serde(rename = "Warn")] + Warn, + #[serde(rename = "Error")] + Error, +} + +#[derive(Debug)] +pub enum LogEntry { + Single { + raw: RawLogEntry, + }, + Sub { + enter: RawLogEntry, + sub_entries: Vec>, + exit: RawLogEntry, + + count_sub: OnceLock, + }, +} + +impl LogEntry { + pub fn get(&self, index: usize) -> Option> { + match self { + LogEntry::Single { .. } => None, + LogEntry::Sub { sub_entries, .. } => sub_entries.get(index).cloned(), + } + } + + pub fn all_fields(&self) -> LogFields { + match self { + LogEntry::Single { raw } => raw.all_fields(), + LogEntry::Sub { enter, exit, .. } => enter.all_fields().merge(&exit.all_fields()), + } + } + + pub fn count(&self) -> usize { + match self { + LogEntry::Single { .. } => 1, + LogEntry::Sub { + sub_entries, + count_sub, + .. + } => { + *count_sub.get_or_init(|| sub_entries.iter().map(|i| i.count()).sum::() + 1) + } + } + } + + pub fn line_text(&self, accessed: bool) -> Line<'static> { + const NO_MESSAGE: &str = ""; + match self { + LogEntry::Single { raw } => { + format!(" ┃{}", raw.fields.message().unwrap_or(NO_MESSAGE)).into() + } + LogEntry::Sub { + enter, sub_entries, .. + } => { + if let Some(val) = enter.all_fields().fields.get("name") + && let Some(s) = val.as_str() + { + Line::from(format!( + "{:3}⭣{:3}⇊ ┃↪ {s}", + sub_entries.len(), + self.count().wrapping_sub(1) + )) + } else { + format!( + " ┃{}", + enter.fields.message().unwrap_or(NO_MESSAGE) + ) + .into() + } + } + } + } +} + +#[derive(Deserialize, Debug, Clone)] +pub struct LogFields { + #[serde(flatten)] + pub fields: BTreeMap, +} + +impl LogFields { + pub fn message(&self) -> Option<&str> { + self.fields.get("message").and_then(|i| i.as_str()) + } + + pub fn merge(&self, other: &Self) -> Self { + Self { + fields: self + .fields + .iter() + .chain(other.fields.iter()) + .map(|(k, v)| (k.clone(), v.clone())) + .collect(), + } + } +} + +#[derive(Deserialize, Debug)] +pub struct RawLogEntry { + pub timestamp: Timestamp, + pub level: Level, + pub filename: String, + pub line_number: usize, + pub fields: LogFields, + #[serde(default)] + pub spans: Vec, +} + +impl RawLogEntry { + pub fn all_fields(&self) -> LogFields { + let mut res = self.fields.clone(); + for i in &self.spans { + res = res.merge(i); + } + res + } +} diff --git a/src/tui/reader.rs b/src/tui/reader.rs new file mode 100644 index 0000000..6f4796d --- /dev/null +++ b/src/tui/reader.rs @@ -0,0 +1,163 @@ +use std::{ + fs::File, + io::{self, BufRead, BufReader}, + mem, + path::{Path, PathBuf}, + rc::Rc, + sync::OnceLock, +}; + +use crate::tui::{ + filter::{Filter, FilterKind}, + model::{LogEntry, RawLogEntry}, +}; + +pub struct LogfileReader { + pub path: PathBuf, + file: BufReader, + + entries: Vec>, + filters: Vec>, +} + +impl LogfileReader { + pub fn new(p: &Path) -> io::Result { + Ok(Self { + file: BufReader::new(File::open(p)?), + path: p.to_path_buf(), + entries: Vec::new(), + filters: Vec::new(), + }) + } + + pub fn add_filter(&mut self, filter: Filter) { + self.filters.push(Rc::new(filter)); + } + + fn next_line(&mut self) -> Option { + let mut res = String::new(); + match self.file.read_line(&mut res) { + Err(e) => { + eprintln!("error: {e:?}"); + None + } + Ok(0) => None, + Ok(_) => Some(res), + } + } + + fn next_raw_entry(&mut self) -> Option { + let line = self.next_line()?; + match serde_json::from_str(&line) { + Ok(i) => Some(i), + Err(e) => { + eprintln!("deserializing: {e:?} in {line}"); + None + } + } + } + + fn next_entry(&mut self) -> Option> { + let mut stack = Vec::<(RawLogEntry, Vec>)>::new(); + let mut curr = Vec::>::new(); + + loop { + let entry = self.next_raw_entry()?; + + let new_entry = Rc::new(match entry.fields.message() { + Some("enter") => { + stack.push((entry, mem::take(&mut curr))); + continue; + } + Some("exit") => { + // TODO: does it match? + let Some((enter, prev)) = stack.pop() else { + panic!("exit before entry"); + }; + let sub_entries = mem::replace(&mut curr, prev); + LogEntry::Sub { + enter: enter, + sub_entries, + exit: entry, + count_sub: OnceLock::new(), + } + } + _ => LogEntry::Single { raw: entry }, + }); + + if stack.is_empty() { + return Some(new_entry); + } else { + curr.push(new_entry); + } + } + } + + pub fn iter_from(&mut self, start: usize) -> FilterAdapter> { + FilterAdapter { + filters: self.filters.clone(), + inner: EntryIterator { + curr: start, + reader: self, + }, + } + } + + fn add_next_entry(&mut self) -> Option<()> { + let entry = self.next_entry()?; + self.entries.push(entry); + Some(()) + } +} + +pub struct EntryIterator<'a> { + curr: usize, + reader: &'a mut LogfileReader, +} + +impl<'a> Iterator for EntryIterator<'a> { + type Item = Rc; + + fn next(&mut self) -> Option { + while self.reader.entries.len() <= self.curr { + self.reader.add_next_entry()?; + } + + let res = Rc::clone(&self.reader.entries[self.curr]); + self.curr += 1; + Some(res) + } +} + +pub struct FilterAdapter { + filters: Vec>, + inner: I, +} + +impl FilterAdapter { + pub fn new(file: &LogfileReader, inner: I) -> FilterAdapter { + Self { + filters: file.filters.clone(), + inner: inner.into_iter(), + } + } +} + +impl Iterator for FilterAdapter { + type Item = I::Item; + + fn next(&mut self) -> Option { + 'next_entry: loop { + let res = self.inner.next()?; + + for filter in &self.reader.filters { + if let FilterKind::Remove = filter.kind + && filter.matcher.matches(&res) + { + continue 'next_entry; + } + } + break Some(res); + } + } +}