diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..5b39223 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,901 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "bitflags" +version = "2.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" + +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "color-eyre" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "compact_str" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags", + "crossterm_winapi", + "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 = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[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.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[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 = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" + +[[package]] +name = "indoc" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" + +[[package]] +name = "instability" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a" +dependencies = [ + "darling", + "indoc", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.175" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.59.0", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "owo-colors" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ratatui" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +dependencies = [ + "bitflags", + "cassowary", + "compact_str", + "crossterm", + "indoc", + "instability", + "itertools", + "lru", + "paste", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.2.0", +] + +[[package]] +name = "redox_syscall" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[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.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.143" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + +[[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 = "steckbrett" +version = "0.1.0" +dependencies = [ + "color-eyre", + "ratatui", + "serde", + "serde_json", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[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 = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools", + "unicode-segmentation", + "unicode-width 0.1.14", +] + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[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.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2bb7a23 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "steckbrett" +version = "0.1.0" +edition = "2024" + +[dependencies] +color-eyre = "0.6.5" +ratatui = "0.29.0" +serde = { version = "1.0.219", features = ["derive"] } +serde_json = "1.0.140" + +[lints.rust] +non_snake_case = "allow" diff --git a/src/app/entry.rs b/src/app/entry.rs new file mode 100644 index 0000000..db0beb1 --- /dev/null +++ b/src/app/entry.rs @@ -0,0 +1,42 @@ +use serde::{Deserialize, Serialize}; +use std::{ + error::Error, + fmt::Display, + io::ErrorKind, + num::{IntErrorKind, ParseIntError}, +}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Entry { + fromIP: String, + fromPort: String, + toIP: String, + toPort: String, +} + +impl Display for Entry { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}:{} → {}:{}", + self.fromIP, self.fromPort, self.toIP, self.toPort + ) + } +} + +impl Entry { + pub fn new(fromIP: String, toIP: String, fromPort: String, toPort: String) -> Option { + if fromPort.parse::().is_ok_and(|a| a > 1 && a < 65535) + && toPort.parse::().is_ok_and(|a| a > 1 && a < 65535) + { + Some(Entry { + fromIP, + toIP, + fromPort, + toPort, + }) + } else { + None + } + } +} diff --git a/src/app/mod.rs b/src/app/mod.rs new file mode 100644 index 0000000..15c75fb --- /dev/null +++ b/src/app/mod.rs @@ -0,0 +1,108 @@ +pub mod entry; + +mod settings; + +pub mod status; +use crate::app::{ + entry::Entry, + settings::Settings, + status::{CurrentScreen, EditingField, EntryCreation}, +}; + +pub struct AppState { + pub fromIP: String, + pub fromPort: String, + pub toIP: String, + pub toPort: String, + pub screen: CurrentScreen, + pub field: Option, + // pub current: Option, + pub currentlyEditing: Option, + pub entries: Vec, + pub confDir: String, +} + +impl AppState { + pub fn new(confDir: String) -> Self { + let settings = Settings::new(&confDir); + AppState { + fromIP: String::new(), + fromPort: String::new(), + toIP: String::new(), + toPort: String::new(), + currentlyEditing: None, + screen: CurrentScreen::Main, + field: None, + entries: settings.entries, + confDir: confDir, + } + } + + pub fn store(&mut self) -> EntryCreation { + match Entry::new( + self.fromIP.clone(), + self.toIP.clone(), + self.fromPort.clone(), + self.toPort.clone(), + ) { + Some(entry) => { + self.entries.push(entry); + self.fromIP = String::new(); + self.toIP = String::new(); + self.fromPort = String::new(); + self.toPort = String::new(); + self.currentlyEditing = None; + + EntryCreation::Success + } + _ => EntryCreation::PortValidationError, + } + } + + pub fn startEditing(&mut self) { + if let Some(currentField) = &self.currentlyEditing { + match currentField { + EditingField::ToIP => {} + _ => self.currentlyEditing = Some(EditingField::FromIP), + } + } else { + self.currentlyEditing = Some(EditingField::FromIP); + } + } + + pub fn nextField(&mut self) { + if let Some(currentField) = &self.currentlyEditing { + self.currentlyEditing = match currentField { + EditingField::FromIP => Some(EditingField::FromPort), + EditingField::FromPort => Some(EditingField::ToIP), + EditingField::ToIP => Some(EditingField::ToPort), + EditingField::ToPort => Some(EditingField::FromIP), + }; + } + } + + pub fn prevField(&mut self) { + if let Some(currentField) = &self.currentlyEditing { + self.currentlyEditing = match currentField { + EditingField::FromIP => Some(EditingField::ToPort), + EditingField::FromPort => Some(EditingField::FromIP), + EditingField::ToIP => Some(EditingField::FromPort), + EditingField::ToPort => Some(EditingField::ToIP), + }; + } + } + + pub fn print(&self) { + let tempEntry = &self.entries[0]; + let res = serde_json::to_string(tempEntry).unwrap(); + println!("{res}"); + let resString = tempEntry.to_string(); + println!("{resString}"); + } + + pub fn save(&self) { + let mut settings = Settings::new(&self.confDir); + settings.entries = self.entries.clone(); + settings.save(&self.confDir); + } +} diff --git a/src/app/settings.rs b/src/app/settings.rs new file mode 100644 index 0000000..5868c3c --- /dev/null +++ b/src/app/settings.rs @@ -0,0 +1,38 @@ +use crate::app::entry::Entry; +use serde::{Deserialize, Serialize}; +use std::fmt::Display; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Settings { + pub entries: Vec, +} + +impl Display for Settings { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Total Entries: {}", self.entries.len()) + } +} + +impl Settings { + pub fn new(config: &String) -> Self { + let data = std::fs::read_to_string(config); + match data { + Ok(data) => { + let settings = + serde_json::from_str::(&data).expect("Settings file corrupted"); + settings + } + Err(_) => { + let newSet = Settings { entries: vec![] }; + let payload = serde_json::to_string_pretty(&newSet).unwrap(); + std::fs::write(config, payload); + newSet + } + } + } + + pub fn save(&self, config: &String) { + let payload = serde_json::to_string(self).unwrap(); + std::fs::write(config, payload); + } +} diff --git a/src/app/status.rs b/src/app/status.rs new file mode 100644 index 0000000..ed86c38 --- /dev/null +++ b/src/app/status.rs @@ -0,0 +1,19 @@ +pub enum CurrentScreen { + Main, + Add, + Settings, + Exit, +} + +pub enum EditingField { + FromIP, + FromPort, + ToIP, + ToPort, +} + +pub enum EntryCreation { + Success, + PortValidationError, + IPValidationError, +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..9747484 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,164 @@ +#[allow(non_snake_case)] +mod app; +mod ui; + +use crate::ui::ui; +use app::AppState; +use color_eyre::Result; +use ratatui::Terminal; +use ratatui::crossterm::event::{ + self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyModifiers, +}; +use ratatui::crossterm::execute; +use ratatui::crossterm::terminal::{ + EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode, +}; +use ratatui::prelude::{Backend, CrosstermBackend}; +use std::io; + +use crate::app::status::{CurrentScreen, EditingField}; + +fn main() -> Result<()> { + enable_raw_mode()?; + let mut stderr = io::stderr(); // This is a special case. Normally using stdout is fine + execute!(stderr, EnterAlternateScreen, EnableMouseCapture)?; + let backend = CrosstermBackend::new(stderr); + let mut terminal = Terminal::new(backend)?; + + // create app and run it + let mut app = AppState::new("./conf.json".to_string()); + // app.load(); + let res = runApp(&mut app, &mut terminal); + disable_raw_mode()?; + execute!( + terminal.backend_mut(), + LeaveAlternateScreen, + DisableMouseCapture + )?; + terminal.show_cursor()?; + + if let Ok(do_print) = res { + if do_print { + app.print(); + } + } else if let Err(err) = res { + println!("{err:?}"); + } + Ok(()) +} + +fn runApp(app: &mut AppState, terminal: &mut Terminal) -> Result { + loop { + terminal.draw(|f| ui(f, app))?; // from ui.rs + if let Event::Key(key) = event::read()? { + // println!("{key:?}"); + if key.kind == event::KeyEventKind::Release { + continue; + } + + match app.screen { + CurrentScreen::Settings => match key.code { + KeyCode::Home | KeyCode::Char('m') | KeyCode::Esc => { + app.screen = CurrentScreen::Main; + } + _ => {} + }, + CurrentScreen::Main => match key.code { + KeyCode::Char('a') => { + app.screen = CurrentScreen::Add; + app.currentlyEditing = Some(EditingField::FromIP); + } + KeyCode::Char('q') | KeyCode::F(10) | KeyCode::Esc => { + app.screen = CurrentScreen::Exit + } + + KeyCode::Char('s') | KeyCode::F(2) => app.screen = CurrentScreen::Settings, + + _ => {} + }, + CurrentScreen::Add => match (key.modifiers, key.code) { + (KeyModifiers::NONE, KeyCode::Enter) => { + if let Some(eF) = &app.currentlyEditing { + match eF { + EditingField::ToPort => { + app.store(); + app.screen = CurrentScreen::Main; + app.currentlyEditing = None; + } + _ => app.nextField(), + } + } + } + (KeyModifiers::NONE, KeyCode::Backspace) => { + if let Some(eF) = &app.currentlyEditing { + match eF { + EditingField::FromIP => { + app.fromIP.pop(); + } + EditingField::FromPort => { + app.fromPort.pop(); + } + EditingField::ToIP => { + app.toIP.pop(); + } + EditingField::ToPort => { + app.toPort.pop(); + } + }; + } + } + (KeyModifiers::NONE, KeyCode::Esc) => { + app.screen = CurrentScreen::Main; + app.currentlyEditing = None; + } + (KeyModifiers::NONE, KeyCode::Tab) => app.nextField(), + (KeyModifiers::SHIFT, KeyCode::Tab) => app.prevField(), + + (m, KeyCode::Char(v)) => { + if let Some(e) = &app.currentlyEditing { + let mut isIP = false; + let opField = match e { + EditingField::FromIP => { + isIP = true; + &mut app.fromIP + } + EditingField::FromPort => &mut app.fromPort, + EditingField::ToIP => { + isIP = true; + &mut app.toIP + } + EditingField::ToPort => &mut app.toPort, + }; + match v { + 'l' => { + if isIP { + opField.clear(); + opField.push_str("127.0.0.1"); + } + } + 'c' => opField.clear(), + 'C' => { + app.fromIP.clear(); + app.toIP.clear(); + app.fromPort.clear(); + app.toPort.clear(); + } + _ => opField.push(v), + } + } + } + _ => {} + }, + CurrentScreen::Exit => match key.code { + KeyCode::Enter => { + app.save(); + return Ok(true); + } + KeyCode::Esc | KeyCode::Char('m') => app.screen = CurrentScreen::Main, + _ => {} + }, + } + } + } + // app.run(terminal) +} diff --git a/src/ui.rs b/src/ui.rs new file mode 100644 index 0000000..9f13007 --- /dev/null +++ b/src/ui.rs @@ -0,0 +1,194 @@ +#[allow(non_snake_case)] +use ratatui::{ + Frame, + layout::{Constraint, Layout}, + style::{Color, Style}, + text::{Line, Span, Text}, + widgets::{Block, Borders, List, ListItem, Paragraph}, +}; +use ratatui::{ + layout::{Direction, Rect}, + widgets::Wrap, +}; + +use crate::app::status::CurrentScreen; +use crate::app::{AppState, status::EditingField}; + +pub fn ui(frame: &mut Frame, app: &AppState) { + let chunks = Layout::default() + .direction(ratatui::layout::Direction::Vertical) + .constraints([ + Constraint::Length(3), + Constraint::Min(3), + Constraint::Length(3), + ]) + .split(frame.area()); + + let footerChunks = Layout::default() + .direction(ratatui::layout::Direction::Horizontal) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(chunks[2]); + + let titleBlock = Block::default() + .borders(Borders::ALL) + .style(Style::default()); + + let title = Paragraph::new(Text::styled( + "Create new entry", + Style::default().fg(Color::Green), + )) + .block(titleBlock); + + frame.render_widget(title, chunks[0]); + + let mut currentItems = Vec::::new(); + for (i, e) in app.entries.iter().enumerate() { + currentItems.push(ListItem::new(Line::from(Span::styled( + format!("{}. {}", i, e), + Style::default().fg(Color::Yellow), + )))); + } + let list = List::new(currentItems); + frame.render_widget(list, chunks[1]); + + if let CurrentScreen::Exit = app.screen { + let exitPopup = Block::default() + .title("Save and Exit?") + .borders(Borders::ALL) + .style(Style::default().bg(Color::DarkGray)); + + let exitText = Text::styled( + "Save the current config and exit? (enter/esc)", + Style::default().fg(Color::Red), + ); + let exitPara = Paragraph::new(exitText) + .block(exitPopup) + .wrap(Wrap { trim: false }); + + let area = centered_rect(60, 25, frame.area()); + frame.render_widget(exitPara, area); + } + + if let Some(edit) = &app.currentlyEditing { + let popup = Block::default() + .title("Add an entry") + .borders(Borders::NONE) + .style(Style::default().bg(Color::DarkGray)); + let area = centered_rect(60, 25, frame.area()); + frame.render_widget(popup, area); + + let fields = Layout::default() + .direction(Direction::Horizontal) + .margin(1) + .constraints([ + Constraint::Percentage(25), + Constraint::Percentage(25), + Constraint::Percentage(25), + Constraint::Percentage(25), + ]) + .split(area); + + let mut fromIPBlock = Block::default().title("From IP").borders(Borders::ALL); + let mut toIPBlock = Block::default().title("To IP").borders(Borders::ALL); + let mut fromPortBlock = Block::default().title("From Port").borders(Borders::ALL); + let mut toPortBlock = Block::default().title("To Port").borders(Borders::ALL); + + let activeStyle = Style::default().bg(Color::LightYellow).fg(Color::Black); + + match edit { + EditingField::FromIP => fromIPBlock = fromIPBlock.style(activeStyle), + EditingField::ToIP => toIPBlock = toIPBlock.style(activeStyle), + EditingField::FromPort => fromPortBlock = fromPortBlock.style(activeStyle), + EditingField::ToPort => toPortBlock = toPortBlock.style(activeStyle), + } + + let fromIPPara = Paragraph::new(app.fromIP.clone()).block(fromIPBlock); + let toIPPara = Paragraph::new(app.toIP.clone()).block(toIPBlock); + let fromPortPara = Paragraph::new(app.fromPort.clone()).block(fromPortBlock); + let toPortPara = Paragraph::new(app.toPort.clone()).block(toPortBlock); + + frame.render_widget(fromIPPara, fields[0]); + frame.render_widget(fromPortPara, fields[1]); + frame.render_widget(toIPPara, fields[2]); + frame.render_widget(toPortPara, fields[3]); + }; + + let screenHelp = vec![ + match app.screen { + CurrentScreen::Main => Span::styled("Main Screen", Style::default().fg(Color::Green)), + CurrentScreen::Add => Span::styled("Add entry", Style::default().fg(Color::Yellow)), + CurrentScreen::Exit => { + Span::styled("Exit Screen", Style::default().fg(Color::LightRed)) + } + CurrentScreen::Settings => Span::styled("Settings", Style::default().fg(Color::Blue)), + } + .to_owned(), + Span::styled(" | ", Style::default().fg(Color::White)), + { + if let Some(editing) = &app.currentlyEditing { + let curEdit = match editing { + EditingField::FromIP => "From IP", + EditingField::ToIP => "To IP", + EditingField::FromPort => "From Port", + EditingField::ToPort => "To Port", + _ => "Something ", + }; + Span::styled( + format!("Editing: {curEdit}"), + Style::default().fg(Color::Green), + ) + } else { + Span::styled("Not Editing", Style::default().fg(Color::DarkGray)) + } + }, + ]; + + let helpFooter = + Paragraph::new(Line::from(screenHelp)).block(Block::default().borders(Borders::ALL)); + + let keybinds = { + match app.screen { + CurrentScreen::Main => Span::styled( + "(a) add entry / (q) save and quit", + Style::default().fg(Color::Red), + ), + CurrentScreen::Add => Span::styled( + "(l) localhost / (enter/tab) next field / (c) clear / (C) clear all / (esc) main menu", + Style::default().fg(Color::Red), + ), + CurrentScreen::Settings => Span::styled("", Style::default().fg(Color::Red)), + CurrentScreen::Exit => Span::styled( + "(enter) save and exit / (esc) main menu", + Style::default().fg(Color::Red), + ), + } + }; + + let keyBindFooter = + Paragraph::new(Line::from(keybinds)).block(Block::default().borders(Borders::ALL)); + + frame.render_widget(helpFooter, footerChunks[0]); + frame.render_widget(keyBindFooter, footerChunks[1]); +} + +fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect { + // Cut the given rectangle into three vertical pieces + let popup_layout = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Percentage((100 - percent_y) / 2), + Constraint::Percentage(percent_y), + Constraint::Percentage((100 - percent_y) / 2), + ]) + .split(r); + + // Then cut the middle vertical piece into three width-wise pieces + Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Percentage((100 - percent_x) / 2), + Constraint::Percentage(percent_x), + Constraint::Percentage((100 - percent_x) / 2), + ]) + .split(popup_layout[1])[1] // Return the middle chunk +}