diff --git a/.gitignore b/.gitignore index 8d5254c..482a3a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target +/*/target .data/*.log diff --git a/Cargo.lock b/Cargo.lock index 835fa64..14defc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -46,15 +46,15 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -67,43 +67,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "once_cell", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.90" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37bf3594c4c988a53154954629820791dde498571819ae4ca50ca811e060cc95" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "arc-swap" @@ -112,14 +113,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] -name = "async-trait" -version = "0.1.83" +name = "arraydeque" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[package]] +name = "async-trait" +version = "0.1.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.96", ] [[package]] @@ -161,9 +168,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" dependencies = [ "serde", ] @@ -179,20 +186,26 @@ dependencies = [ [[package]] name = "bstr" -version = "1.10.0" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" dependencies = [ "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "serde", ] [[package]] -name = "bytes" -version = "1.8.0" +name = "byteorder" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "camino" @@ -205,18 +218,18 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] [[package]] name = "cargo_metadata" -version = "0.18.1" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +checksum = "8769706aad5d996120af43197bf46ef6ad0fda35216b4505f926a365a232d924" dependencies = [ "camino", "cargo-platform", @@ -243,9 +256,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.31" +version = "1.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" +checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" dependencies = [ "shlex", ] @@ -258,9 +271,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.20" +version = "4.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" dependencies = [ "clap_builder", "clap_derive", @@ -268,9 +281,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.20" +version = "4.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" dependencies = [ "anstream", "anstyle", @@ -283,21 +296,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.96", ] [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "clru" @@ -334,28 +347,15 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "compact_str" -version = "0.7.1" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" -dependencies = [ - "castaway", - "cfg-if", - "itoa", - "ryu", - "static_assertions", -] - -[[package]] -name = "compact_str" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" dependencies = [ "castaway", "cfg-if", @@ -368,34 +368,33 @@ dependencies = [ [[package]] name = "config" -version = "0.14.0" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be" +checksum = "e329294a796e9b22329669c1f433a746983f9e324e07f4ef135be81bb2262de4" dependencies = [ "async-trait", "convert_case", "json5", - "lazy_static", - "nom", "pathdiff", "ron", "rust-ini", "serde", "serde_json", "toml", - "yaml-rust", + "winnow", + "yaml-rust2", ] [[package]] name = "console" -version = "0.15.8" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" dependencies = [ "encode_unicode", - "lazy_static", "libc", - "windows-sys 0.52.0", + "once_cell", + "windows-sys 0.59.0", ] [[package]] @@ -427,11 +426,48 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core" +version = "0.2.0" +dependencies = [ + "anyhow", + "better-panic", + "clap", + "color-eyre", + "config", + "crossterm", + "derive_deref", + "directories", + "futures", + "human-panic", + "json5", + "lazy_static", + "libc", + "once_cell", + "pretty_assertions", + "ratatui", + "raw-cpuid", + "regex", + "serde", + "serde_json", + "signal-hook", + "strip-ansi-escapes", + "strum", + "tempfile", + "tokio", + "tokio-util", + "tracing", + "tracing-error", + "tracing-subscriber", + "vergen-gix", + "walkdir", +] + [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -445,23 +481,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossterm" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" -dependencies = [ - "bitflags", - "crossterm_winapi", - "futures-core", - "libc", - "mio 0.8.11", - "parking_lot", - "signal-hook", - "signal-hook-mio", - "winapi", -] - [[package]] name = "crossterm" version = "0.28.1" @@ -471,7 +490,7 @@ dependencies = [ "bitflags", "crossterm_winapi", "futures-core", - "mio 1.0.2", + "mio", "parking_lot", "rustix", "serde", @@ -526,7 +545,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.82", + "syn 2.0.96", ] [[package]] @@ -537,7 +556,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.82", + "syn 2.0.96", ] [[package]] @@ -545,36 +564,19 @@ name = "deja-vu" version = "0.2.0" dependencies = [ "anyhow", - "better-panic", "clap", "color-eyre", - "config", - "crossterm 0.28.1", - "derive_deref", - "directories", - "futures", - "human-panic", - "json5", - "lazy_static", - "libc", - "once_cell", - "pretty_assertions", - "ratatui 0.28.1", - "raw-cpuid", - "regex", + "core", + "rand", + "ratatui", "serde", "serde_json", - "signal-hook", - "strip-ansi-escapes", - "strum", - "tempfile", "tokio", "tokio-util", "tracing", "tracing-error", "tracing-subscriber", "vergen-gix", - "walkdir", ] [[package]] @@ -604,7 +606,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.96", ] [[package]] @@ -614,7 +616,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.82", + "syn 2.0.96", ] [[package]] @@ -646,23 +648,34 @@ dependencies = [ [[package]] name = "directories" -version = "5.0.1" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.48.0", + "windows-sys 0.59.0", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", ] [[package]] @@ -688,9 +701,18 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] [[package]] name = "equivalent" @@ -700,12 +722,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -723,12 +745,15 @@ name = "faster-hex" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" +dependencies = [ + "serde", +] [[package]] name = "fastrand" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "filetime" @@ -744,12 +769,12 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.34" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", - "miniz_oxide 0.8.0", + "miniz_oxide 0.8.3", ] [[package]] @@ -760,9 +785,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" [[package]] name = "form_urlencoded" @@ -829,7 +854,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.96", ] [[package]] @@ -891,9 +916,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "gix" -version = "0.66.0" +version = "0.69.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9048b8d1ae2104f045cb37e5c450fc49d5d8af22609386bfc739c11ba88995eb" +checksum = "8d0eebdaecdcf405d5433a36f85e4f058cf4de48ee2604388be0dbccbaad353e" dependencies = [ "gix-actor", "gix-commitgraph", @@ -912,11 +937,13 @@ dependencies = [ "gix-odb", "gix-pack", "gix-path", + "gix-protocol", "gix-ref", "gix-refspec", "gix-revision", "gix-revwalk", "gix-sec", + "gix-shallow", "gix-tempfile", "gix-trace", "gix-traverse", @@ -932,9 +959,9 @@ dependencies = [ [[package]] name = "gix-actor" -version = "0.32.0" +version = "0.33.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc19e312cd45c4a66cd003f909163dc2f8e1623e30a0c0c6df3776e89b308665" +checksum = "20018a1a6332e065f1fcc8305c1c932c6b8c9985edea2284b3c79dc6fa3ee4b2" dependencies = [ "bstr", "gix-date", @@ -946,27 +973,39 @@ dependencies = [ [[package]] name = "gix-bitmap" -version = "0.2.11" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a371db66cbd4e13f0ed9dc4c0fea712d7276805fccc877f77e96374d317e87ae" +checksum = "b1db9765c69502650da68f0804e3dc2b5f8ccc6a2d104ca6c85bc40700d37540" dependencies = [ "thiserror", ] [[package]] name = "gix-chunk" -version = "0.4.8" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c8751169961ba7640b513c3b24af61aa962c967aaf04116734975cd5af0c52" +checksum = "0b1f1d8764958699dc764e3f727cef280ff4d1bd92c107bbf8acd85b30c1bd6f" dependencies = [ "thiserror", ] [[package]] -name = "gix-commitgraph" -version = "0.24.3" +name = "gix-command" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "133b06f67f565836ec0c473e2116a60fb74f80b6435e21d88013ac0e3c60fc78" +checksum = "cb410b84d6575db45e62025a9118bdbf4d4b099ce7575a76161e898d9ca98df1" +dependencies = [ + "bstr", + "gix-path", + "gix-trace", + "shell-words", +] + +[[package]] +name = "gix-commitgraph" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8da6591a7868fb2b6dabddea6b09988b0b05e0213f938dbaa11a03dd7a48d85" dependencies = [ "bstr", "gix-chunk", @@ -978,9 +1017,9 @@ dependencies = [ [[package]] name = "gix-config" -version = "0.40.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78e797487e6ca3552491de1131b4f72202f282fb33f198b1c34406d765b42bb0" +checksum = "6649b406ca1f99cb148959cf00468b231f07950f8ec438cc0903cda563606f19" dependencies = [ "bstr", "gix-config-value", @@ -999,9 +1038,9 @@ dependencies = [ [[package]] name = "gix-config-value" -version = "0.14.8" +version = "0.14.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03f76169faa0dec598eac60f83d7fcdd739ec16596eca8fb144c88973dbe6f8c" +checksum = "11365144ef93082f3403471dbaa94cfe4b5e72743bdb9560719a251d439f4cee" dependencies = [ "bitflags", "bstr", @@ -1012,9 +1051,9 @@ dependencies = [ [[package]] name = "gix-date" -version = "0.9.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c84b7af01e68daf7a6bb8bb909c1ff5edb3ce4326f1f43063a5a96d3c3c8a5" +checksum = "c57c477b645ee248b173bb1176b52dd528872f12c50375801a58aaf5ae91113f" dependencies = [ "bstr", "itoa", @@ -1024,9 +1063,9 @@ dependencies = [ [[package]] name = "gix-diff" -version = "0.46.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92c9afd80fff00f8b38b1c1928442feb4cd6d2232a6ed806b6b193151a3d336c" +checksum = "a8e92566eccbca205a0a0f96ffb0327c061e85bc5c95abbcddfe177498aa04f6" dependencies = [ "bstr", "gix-hash", @@ -1036,9 +1075,9 @@ dependencies = [ [[package]] name = "gix-discover" -version = "0.35.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0577366b9567376bc26e815fd74451ebd0e6218814e242f8e5b7072c58d956d2" +checksum = "83bf6dfa4e266a4a9becb4d18fc801f92c3f7cc6c433dd86fdadbcf315ffb6ef" dependencies = [ "bstr", "dunce", @@ -1052,9 +1091,9 @@ dependencies = [ [[package]] name = "gix-features" -version = "0.38.2" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac7045ac9fe5f9c727f38799d002a7ed3583cd777e3322a7c4b43e3cf437dc69" +checksum = "7d85d673f2e022a340dba4713bed77ef2cf4cd737d2f3e0f159d45e0935fd81f" dependencies = [ "crc32fast", "flate2", @@ -1071,9 +1110,9 @@ dependencies = [ [[package]] name = "gix-fs" -version = "0.11.3" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2bfe6249cfea6d0c0e0990d5226a4cb36f030444ba9e35e0639275db8f98575" +checksum = "3b3d4fac505a621f97e5ce2c69fdc425742af00c0920363ca4074f0eb48b1db9" dependencies = [ "fastrand", "gix-features", @@ -1082,9 +1121,9 @@ dependencies = [ [[package]] name = "gix-glob" -version = "0.16.5" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74908b4bbc0a0a40852737e5d7889f676f081e340d5451a16e5b4c50d592f111" +checksum = "aaf69a6bec0a3581567484bf99a4003afcaf6c469fd4214352517ea355cf3435" dependencies = [ "bitflags", "bstr", @@ -1094,9 +1133,9 @@ dependencies = [ [[package]] name = "gix-hash" -version = "0.14.2" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93d7df7366121b5018f947a04d37f034717e113dcf9ccd85c34b58e57a74d5e" +checksum = "0b5eccc17194ed0e67d49285e4853307e4147e95407f91c1c3e4a13ba9f4e4ce" dependencies = [ "faster-hex", "thiserror", @@ -1104,9 +1143,9 @@ dependencies = [ [[package]] name = "gix-hashtable" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddf80e16f3c19ac06ce415a38b8591993d3f73aede049cb561becb5b3a8e242" +checksum = "0ef65b256631078ef733bc5530c4e6b1c2e7d5c2830b75d4e9034ab3997d18fe" dependencies = [ "gix-hash", "hashbrown 0.14.5", @@ -1115,9 +1154,9 @@ dependencies = [ [[package]] name = "gix-index" -version = "0.35.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cd4203244444017682176e65fd0180be9298e58ed90bd4a8489a357795ed22d" +checksum = "270645fd20556b64c8ffa1540d921b281e6994413a0ca068596f97e9367a257a" dependencies = [ "bitflags", "bstr", @@ -1143,9 +1182,9 @@ dependencies = [ [[package]] name = "gix-lock" -version = "14.0.0" +version = "15.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bc7fe297f1f4614774989c00ec8b1add59571dc9b024b4c00acb7dedd4e19d" +checksum = "1cd3ab68a452db63d9f3ebdacb10f30dba1fa0d31ac64f4203d395ed1102d940" dependencies = [ "gix-tempfile", "gix-utils", @@ -1154,15 +1193,17 @@ dependencies = [ [[package]] name = "gix-object" -version = "0.44.0" +version = "0.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f5b801834f1de7640731820c2df6ba88d95480dc4ab166a5882f8ff12b88efa" +checksum = "e42d58010183ef033f31088479b4eb92b44fe341b35b62d39eb8b185573d77ea" dependencies = [ "bstr", "gix-actor", "gix-date", "gix-features", "gix-hash", + "gix-hashtable", + "gix-path", "gix-utils", "gix-validate", "itoa", @@ -1173,15 +1214,16 @@ dependencies = [ [[package]] name = "gix-odb" -version = "0.63.0" +version = "0.66.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3158068701c17df54f0ab2adda527f5a6aca38fd5fd80ceb7e3c0a2717ec747" +checksum = "cb780eceb3372ee204469478de02eaa34f6ba98247df0186337e0333de97d0ae" dependencies = [ "arc-swap", "gix-date", "gix-features", "gix-fs", "gix-hash", + "gix-hashtable", "gix-object", "gix-pack", "gix-path", @@ -1193,9 +1235,9 @@ dependencies = [ [[package]] name = "gix-pack" -version = "0.53.0" +version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3223aa342eee21e1e0e403cad8ae9caf9edca55ef84c347738d10681676fd954" +checksum = "4158928929be29cae7ab97afc8e820a932071a7f39d8ba388eed2380c12c566c" dependencies = [ "clru", "gix-chunk", @@ -1210,10 +1252,22 @@ dependencies = [ ] [[package]] -name = "gix-path" -version = "0.10.11" +name = "gix-packetline" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebfc4febd088abdcbc9f1246896e57e37b7a34f6909840045a1767c6dafac7af" +checksum = "c7e5ae6bc3ac160a6bf44a55f5537813ca3ddb08549c0fd3e7ef699c73c439cd" +dependencies = [ + "bstr", + "faster-hex", + "gix-trace", + "thiserror", +] + +[[package]] +name = "gix-path" +version = "0.10.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c40f12bb65a8299be0cfb90fe718e3be236b7a94b434877012980863a883a99f" dependencies = [ "bstr", "gix-trace", @@ -1223,10 +1277,29 @@ dependencies = [ ] [[package]] -name = "gix-quote" -version = "0.4.12" +name = "gix-protocol" +version = "0.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbff4f9b9ea3fa7a25a70ee62f545143abef624ac6aa5884344e70c8b0a1d9ff" +checksum = "c84642e8b6fed7035ce9cc449593019c55b0ec1af7a5dce1ab8a0636eaaeb067" +dependencies = [ + "bstr", + "gix-date", + "gix-features", + "gix-hash", + "gix-ref", + "gix-shallow", + "gix-transport", + "gix-utils", + "maybe-async", + "thiserror", + "winnow", +] + +[[package]] +name = "gix-quote" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e49357fccdb0c85c0d3a3292a9f6db32d9b3535959b5471bb9624908f4a066c6" dependencies = [ "bstr", "gix-utils", @@ -1235,9 +1308,9 @@ dependencies = [ [[package]] name = "gix-ref" -version = "0.47.0" +version = "0.49.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae0d8406ebf9aaa91f55a57f053c5a1ad1a39f60fdf0303142b7be7ea44311e5" +checksum = "a91b61776c839d0f1b7114901179afb0947aa7f4d30793ca1c56d335dfef485f" dependencies = [ "gix-actor", "gix-features", @@ -1256,9 +1329,9 @@ dependencies = [ [[package]] name = "gix-refspec" -version = "0.25.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebb005f82341ba67615ffdd9f7742c87787544441c88090878393d0682869ca6" +checksum = "00c056bb747868c7eb0aeb352c9f9181ab8ca3d0a2550f16470803500c6c413d" dependencies = [ "bstr", "gix-hash", @@ -1270,11 +1343,13 @@ dependencies = [ [[package]] name = "gix-revision" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4621b219ac0cdb9256883030c3d56a6c64a6deaa829a92da73b9a576825e1e" +checksum = "61e1ddc474405a68d2ce8485705dd72fe6ce959f2f5fe718601ead5da2c8f9e7" dependencies = [ + "bitflags", "bstr", + "gix-commitgraph", "gix-date", "gix-hash", "gix-hashtable", @@ -1286,9 +1361,9 @@ dependencies = [ [[package]] name = "gix-revwalk" -version = "0.15.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b41e72544b93084ee682ef3d5b31b1ba4d8fa27a017482900e5e044d5b1b3984" +checksum = "510026fc32f456f8f067d8f37c34088b97a36b2229d88a6a5023ef179fcb109d" dependencies = [ "gix-commitgraph", "gix-date", @@ -1301,9 +1376,9 @@ dependencies = [ [[package]] name = "gix-sec" -version = "0.10.8" +version = "0.10.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe4d52f30a737bbece5276fab5d3a8b276dc2650df963e293d0673be34e7a5f" +checksum = "d84dae13271f4313f8d60a166bf27e54c968c7c33e2ffd31c48cafe5da649875" dependencies = [ "bitflags", "gix-path", @@ -1312,10 +1387,22 @@ dependencies = [ ] [[package]] -name = "gix-tempfile" -version = "14.0.2" +name = "gix-shallow" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046b4927969fa816a150a0cda2e62c80016fe11fb3c3184e4dddf4e542f108aa" +checksum = "88d2673242e87492cb6ff671f0c01f689061ca306c4020f137197f3abc84ce01" +dependencies = [ + "bstr", + "gix-hash", + "gix-lock", + "thiserror", +] + +[[package]] +name = "gix-tempfile" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2feb86ef094cc77a4a9a5afbfe5de626897351bbbd0de3cb9314baf3049adb82" dependencies = [ "gix-fs", "libc", @@ -1328,15 +1415,31 @@ dependencies = [ [[package]] name = "gix-trace" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cae0e8661c3ff92688ce1c8b8058b3efb312aba9492bbe93661a21705ab431b" +checksum = "7c396a2036920c69695f760a65e7f2677267ccf483f25046977d87e4cb2665f7" + +[[package]] +name = "gix-transport" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d91e507a8713cfa2318d5a85d75b36e53a40379cc7eb7634ce400ecacbaf" +dependencies = [ + "bstr", + "gix-command", + "gix-features", + "gix-packetline", + "gix-quote", + "gix-sec", + "gix-url", + "thiserror", +] [[package]] name = "gix-traverse" -version = "0.41.0" +version = "0.43.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030da39af94e4df35472e9318228f36530989327906f38e27807df305fccb780" +checksum = "6ed47d648619e23e93f971d2bba0d10c1100e54ef95d2981d609907a8cabac89" dependencies = [ "bitflags", "gix-commitgraph", @@ -1351,23 +1454,23 @@ dependencies = [ [[package]] name = "gix-url" -version = "0.27.5" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd280c5e84fb22e128ed2a053a0daeacb6379469be6a85e3d518a0636e160c89" +checksum = "d096fb733ba6bd3f5403dba8bd72bdd8809fe2b347b57844040b8f49c93492d9" dependencies = [ "bstr", "gix-features", "gix-path", - "home", + "percent-encoding", "thiserror", "url", ] [[package]] name = "gix-utils" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35192df7fd0fa112263bad8021e2df7167df4cc2a6e6d15892e1e55621d3d4dc" +checksum = "ff08f24e03ac8916c478c8419d7d3c33393da9bb41fa4c24455d5406aeefd35f" dependencies = [ "fastrand", "unicode-normalization", @@ -1375,20 +1478,14 @@ dependencies = [ [[package]] name = "gix-validate" -version = "0.9.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81f2badbb64e57b404593ee26b752c26991910fd0d81fe6f9a71c1a8309b6c86" +checksum = "9eaa01c3337d885617c0a42e92823922a2aea71f4caeace6fe87002bdcadbd90" dependencies = [ "bstr", "thiserror", ] -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" - [[package]] name = "hashbrown" version = "0.14.5" @@ -1401,34 +1498,37 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "allocator-api2", "equivalent", "foldhash", ] +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1447,6 +1547,124 @@ dependencies = [ "uuid", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1455,12 +1673,23 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -1471,22 +1700,31 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown 0.15.2", ] [[package]] -name = "instability" -version = "0.3.2" +name = "indoc" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + +[[package]] +name = "instability" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf9fed6d91cfb734e7476a06bde8300a1b94e217e1b523b6f0cd1a01998c71d" dependencies = [ + "darling", + "indoc", + "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.96", ] [[package]] @@ -1495,15 +1733,6 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.13.0" @@ -1515,31 +1744,35 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jiff" -version = "0.1.13" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a45489186a6123c128fdf6016183fcfab7113e1820eb813127e036e287233fb" +checksum = "d2bb0c2e28117985a4d90e3bc70092bc8f226f434c7ec7e23dd9ff99c5c5721a" dependencies = [ "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", "windows-sys 0.59.0", ] [[package]] name = "jiff-tzdb" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91335e575850c5c4c673b9bd467b0e025f164ca59d0564f69d0c2ee0ffad4653" +checksum = "cf2cec2f5d266af45a071ece48b1fb89f3b00b2421ac3a5fe10285a6caaa60d3" [[package]] name = "jiff-tzdb-platform" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9835f0060a626fe59f160437bc725491a6af23133ea906500027d1bd2f8f4329" +checksum = "a63c62e404e7b92979d2792352d885a7f8f83fd1d0d31eea582d77b2ceca697e" dependencies = [ "jiff-tzdb", ] @@ -1563,9 +1796,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.161" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libredox" @@ -1579,16 +1812,16 @@ dependencies = [ ] [[package]] -name = "linked-hash-map" -version = "0.5.6" +name = "linux-raw-sys" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] -name = "linux-raw-sys" -version = "0.4.14" +name = "litemap" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "lock_api" @@ -1602,9 +1835,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "lru" @@ -1612,7 +1845,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.0", + "hashbrown 0.15.2", ] [[package]] @@ -1624,6 +1857,17 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "maybe-async" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "memchr" version = "2.7.4" @@ -1639,12 +1883,6 @@ dependencies = [ "libc", ] -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.7.4" @@ -1656,48 +1894,25 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ "adler2", ] [[package]] name = "mio" -version = "0.8.11" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "libc", - "log", - "wasi", - "windows-sys 0.48.0", -] - -[[package]] -name = "mio" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" -dependencies = [ - "hermit-abi", "libc", "log", "wasi", "windows-sys 0.52.0", ] -[[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 = "nu-ansi-term" version = "0.46.0" @@ -1746,19 +1961,19 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "ordered-multimap" -version = "0.6.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" dependencies = [ "dlv-list", - "hashbrown 0.13.2", + "hashbrown 0.14.5", ] [[package]] name = "os_info" -version = "3.8.2" +version = "3.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" +checksum = "6e6520c8cc998c5741ee68ec1dc369fc47e5f0ea5320018ecf2a1ccd6328f48b" dependencies = [ "log", "serde", @@ -1797,7 +2012,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -1808,20 +2023,28 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pathdiff" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "pe-menu" -version = "0.1.0" +version = "0.2.0" dependencies = [ - "crossterm 0.27.0", + "anyhow", + "clap", + "color-eyre", + "core", + "crossterm", "futures", - "ratatui 0.26.3", + "ratatui", "serde", "tokio", "toml", + "tracing", + "tracing-error", + "tracing-subscriber", + "vergen-gix", ] [[package]] @@ -1832,9 +2055,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", "thiserror", @@ -1843,9 +2066,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" +checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" dependencies = [ "pest", "pest_generator", @@ -1853,22 +2076,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" +checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.96", ] [[package]] name = "pest_meta" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" +checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" dependencies = [ "once_cell", "pest", @@ -1877,9 +2100,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -1887,12 +2110,36 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "portable-atomic" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + [[package]] name = "pretty_assertions" version = "1.4.1" @@ -1905,93 +2152,107 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.88" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "prodash" -version = "28.0.0" +version = "29.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79" +checksum = "a266d8d6020c61a437be704c5e618037588e1985c7dbb7bf8d265db84cffe325" +dependencies = [ + "log", + "parking_lot", +] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] [[package]] -name = "ratatui" -version = "0.26.3" +name = "rand" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "bitflags", - "cassowary", - "compact_str 0.7.1", - "crossterm 0.27.0", - "itertools 0.12.1", - "lru", - "paste", - "stability", - "strum", - "unicode-segmentation", - "unicode-truncate", - "unicode-width 0.1.14", + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", ] [[package]] name = "ratatui" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdef7f9be5c0122f890d58bdf4d964349ba6a6161f705907526d891efabba57d" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ "bitflags", "cassowary", - "compact_str 0.8.0", - "crossterm 0.28.1", + "compact_str", + "crossterm", + "indoc", "instability", - "itertools 0.13.0", + "itertools", "lru", "paste", "serde", "strum", - "strum_macros", "unicode-segmentation", "unicode-truncate", - "unicode-width 0.1.14", + "unicode-width 0.2.0", ] [[package]] name = "raw-cpuid" -version = "11.2.0" +version = "11.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab240315c661615f2ee9f0f2cd32d5a7343a84d5ebcccb99d46e6637565e7b0" +checksum = "c6928fa44c097620b706542d428957635951bade7143269085389d42c8a4927e" dependencies = [ "bitflags", ] [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ "bitflags", ] [[package]] name = "redox_users" -version = "0.4.6" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ "getrandom", "libredox", @@ -2006,7 +2267,7 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "regex-syntax 0.8.5", ] @@ -2021,9 +2282,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -2056,12 +2317,13 @@ dependencies = [ [[package]] name = "rust-ini" -version = "0.19.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091" +checksum = "4e310ef0e1b6eeb79169a1171daf9abcb87a2e17c03bee2c4bb100b55c75409f" dependencies = [ "cfg-if", "ordered-multimap", + "trim-in-place", ] [[package]] @@ -2072,22 +2334,22 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "rustversion" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "ryu" @@ -2112,38 +2374,38 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" -version = "1.0.23" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.210" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.96", ] [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" dependencies = [ "itoa", "memchr", @@ -2186,6 +2448,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "shlex" version = "1.3.0" @@ -2209,8 +2477,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", - "mio 0.8.11", - "mio 1.0.2", + "mio", "signal-hook", ] @@ -2240,23 +2507,19 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] -name = "stability" -version = "0.2.1" +name = "stable_deref_trait" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" -dependencies = [ - "quote", - "syn 2.0.82", -] +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "static_assertions" @@ -2266,9 +2529,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strip-ansi-escapes" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" dependencies = [ "vte", ] @@ -2298,7 +2561,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.82", + "syn 2.0.96", ] [[package]] @@ -2314,9 +2577,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.82" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -2324,13 +2587,25 @@ dependencies = [ ] [[package]] -name = "tempfile" -version = "3.13.0" +name = "synstructure" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "tempfile" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" dependencies = [ "cfg-if", "fastrand", + "getrandom", "once_cell", "rustix", "windows-sys 0.59.0", @@ -2338,9 +2613,9 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" +checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" dependencies = [ "rustix", "windows-sys 0.59.0", @@ -2348,22 +2623,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.64" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.96", ] [[package]] @@ -2378,9 +2653,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -2401,9 +2676,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -2419,10 +2694,20 @@ dependencies = [ ] [[package]] -name = "tinyvec" -version = "1.8.0" +name = "tinystr" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" dependencies = [ "tinyvec_macros", ] @@ -2435,14 +2720,14 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", "bytes", "libc", - "mio 1.0.2", + "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -2453,20 +2738,20 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.96", ] [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -2511,9 +2796,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -2522,20 +2807,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.96", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -2543,9 +2828,9 @@ dependencies = [ [[package]] name = "tracing-error" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" dependencies = [ "tracing", "tracing-subscriber", @@ -2564,9 +2849,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", @@ -2581,6 +2866,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "trim-in-place" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" + [[package]] name = "typenum" version = "1.17.0" @@ -2595,15 +2886,9 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicase" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" - -[[package]] -name = "unicode-bidi" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-bom" @@ -2613,9 +2898,9 @@ checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-normalization" @@ -2638,7 +2923,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" dependencies = [ - "itertools 0.13.0", + "itertools", "unicode-segmentation", "unicode-width 0.1.14", ] @@ -2657,15 +2942,27 @@ checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -2674,24 +2971,24 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4" dependencies = [ "getrandom", ] [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vergen" -version = "9.0.1" +version = "9.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349ed9e45296a581f455bc18039878f409992999bc1d5da12a6800eb18c8752f" +checksum = "e0d2f179f8075b805a43a2a21728a46f0cc2921b3c58695b28fa8817e103cd9a" dependencies = [ "anyhow", "cargo_metadata", @@ -2704,9 +3001,9 @@ dependencies = [ [[package]] name = "vergen-gix" -version = "1.0.2" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02ef5d49e57c96e025770171c1c7ee0e30cd6f712f21a1fe501a58be6d069192" +checksum = "2b218ac3c56c2c31227bd7813735bfa51eb0dc5212e6d4bab6eac7c8208e5b66" dependencies = [ "anyhow", "derive_builder", @@ -2719,9 +3016,9 @@ dependencies = [ [[package]] name = "vergen-lib" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229eaddb0050920816cf051e619affaf18caa3dd512de8de5839ccbc8e53abb0" +checksum = "9b07e6010c0f3e59fcb164e0163834597da68d1f864e2b8ca49f74de01e9c166" dependencies = [ "anyhow", "derive_builder", @@ -2736,22 +3033,11 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "vte" -version = "0.11.1" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" dependencies = [ - "utf8parse", - "vte_generate_state_changes", -] - -[[package]] -name = "vte_generate_state_changes" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e" -dependencies = [ - "proc-macro2", - "quote", + "memchr", ] [[package]] @@ -2801,22 +3087,13 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -2825,22 +3102,7 @@ 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-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] @@ -2849,46 +3111,28 @@ 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_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "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", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[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_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -2901,48 +3145,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[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_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[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_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -2951,20 +3171,34 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.20" +version = "0.6.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" dependencies = [ "memchr", ] [[package]] -name = "yaml-rust" -version = "0.4.5" +name = "write16" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yaml-rust2" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a1a1c0bc9823338a3bdf8c61f994f23ac004c6fa32c08cd152984499b445e8d" dependencies = [ - "linked-hash-map", + "arraydeque", + "encoding_rs", + "hashlink", ] [[package]] @@ -2973,12 +3207,37 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] @@ -2990,5 +3249,48 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.96", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", ] diff --git a/Cargo.toml b/Cargo.toml index 03c2f03..39fe348 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,6 @@ # along with Deja-vu. If not, see . [workspace] -members = ["deja_vu", "pe_menu"] -description = "Clone/Install Windows, create/edit boot files, and troubleshoot boot issues." +members = ["core", "deja_vu", "pe_menu"] +default-members = ["deja_vu", "pe_menu"] resolver = "2" diff --git a/config/config.json5 b/config/config.json5 new file mode 100644 index 0000000..986126d --- /dev/null +++ b/config/config.json5 @@ -0,0 +1,109 @@ +{ + "app_title": "Deja-vu", + "clone_app_path": "C:/Program Files/Some Clone Tool/app.exe", + "conemu_path": "C:/Program Files/ConEmu/ConEmu64.exe", + "keybindings": { + "Home": { + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, + "ScanDisks": { + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, + "InstallDrivers": { + "": "Process", + "": "KeyUp", + "": "KeyDown", + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, + "SelectDisks": { + "": "InstallDriver", + "": "ScanDisks", + "": "Process", + "": "KeyUp", + "": "KeyDown", + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, + "SelectTableType": { + "": "PrevScreen", + "": "Process", + "": "KeyUp", + "": "KeyDown", + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, + "Confirm": { + "": "PrevScreen", + "": "Process", + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, + "PreClone": { + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, + "Clone": { + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, + "SelectParts": { + "": "Process", + "": "KeyUp", + "": "KeyDown", + "": "ScanDisks", + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, + "PostClone": { + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, + "Done": { + "": "Quit", + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, + "Failed": { + "": "Quit", + "": "Quit", + "": "Quit", + "": "Quit", + "": "Suspend" + }, + "PEMenu": { + "": "Process", + "": "KeyUp", + "": "KeyDown", + "": "Quit", + "": "OpenTerminal", + "": "Quit", + "": "Quit", + "": "Suspend" + }, + }, +} diff --git a/core/Cargo.toml b/core/Cargo.toml new file mode 100644 index 0000000..e724ecf --- /dev/null +++ b/core/Cargo.toml @@ -0,0 +1,63 @@ +# This file is part of Deja-vu. +# +# Deja-vu is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Deja-vu is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Deja-vu. If not, see . + +[package] +name = "core" +authors = ["2Shirt <2xShirt@gmail.com>"] +edition = "2021" +license = "GPL" +version = "0.2.0" + +[dependencies] +better-panic = "0.3.0" +clap = { version = "4.4.5", features = [ + "derive", + "cargo", + "wrap_help", + "unicode", + "string", + "unstable-styles", +] } +color-eyre = "0.6.3" +config = "0.15.6" +crossterm = { version = "0.28.1", features = ["serde", "event-stream"] } +derive_deref = "1.1.1" +directories = "6.0.0" +futures = "0.3.30" +human-panic = "2.0.1" +json5 = "0.4.1" +lazy_static = "1.5.0" +libc = "0.2.158" +once_cell = "1.20.2" +pretty_assertions = "1.4.0" +ratatui = { version = "0.29.0", features = ["serde", "macros"] } +raw-cpuid = "11.2.0" +regex = "1.11.1" +serde = { version = "1.0.217", features = ["derive"] } +serde_json = "1.0.125" +signal-hook = "0.3.17" +strip-ansi-escapes = "0.2.0" +strum = { version = "0.26.3", features = ["derive"] } +tempfile = "3.13.0" +tokio = { version = "1.43.0", features = ["full"] } +tokio-util = "0.7.11" +tracing = "0.1.41" +tracing-error = "0.2.0" +tracing-subscriber = { version = "0.3.18", features = ["env-filter", "serde"] } +walkdir = "2.5.0" + +[build-dependencies] +anyhow = "1.0.86" +vergen-gix = { version = "1.0.0", features = ["build", "cargo"] } diff --git a/core/build.rs b/core/build.rs new file mode 100644 index 0000000..c3ced6b --- /dev/null +++ b/core/build.rs @@ -0,0 +1,28 @@ +// This file is part of Deja-vu. +// +// Deja-vu is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Deja-vu is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Deja-vu. If not, see . +// +use anyhow::Result; +use vergen_gix::{BuildBuilder, CargoBuilder, Emitter, GixBuilder}; + +fn main() -> Result<()> { + let build = BuildBuilder::all_build()?; + let gix = GixBuilder::all_git()?; + let cargo = CargoBuilder::all_cargo()?; + Emitter::default() + .add_instructions(&build)? + .add_instructions(&gix)? + .add_instructions(&cargo)? + .emit() +} diff --git a/deja_vu/src/action.rs b/core/src/action.rs similarity index 60% rename from deja_vu/src/action.rs rename to core/src/action.rs index ba6b760..604edd7 100644 --- a/deja_vu/src/action.rs +++ b/core/src/action.rs @@ -16,26 +16,27 @@ use serde::{Deserialize, Serialize}; use strum::Display; -use crate::{ - app::Mode, - components::popup::Type, - system::{ - disk::{Disk, PartitionTableType}, - drivers::Driver, - }, -}; +use crate::{components::popup::Type, line::DVLine, state::Mode, system::disk::Disk}; -#[derive(Debug, Default, Clone, PartialEq, Eq, Display, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)] pub enum Action { - // App + // App (Clone) + Highlight(usize), InstallDriver, - #[default] Process, ScanDisks, - Select(Option, Option), // indicies for (source, dest) or (boot, os) - SelectDriver(Driver), - SelectTableType(PartitionTableType), + Select(Option, Option), // indicies for (source, dest) etc + SelectRight(Option, Option), // indicies for right info pane UpdateDiskList(Vec), + UpdateFooter(String), + UpdateLeft(String, Vec, Vec, usize), // (title, labels, items, select_num) + // NOTE: select_num should be set to 0, 1, or 2. + // 0: For repeating selections + // 1: For a single choice + // 2: For two selections (obviously) + UpdateRight(Vec>, usize, Vec>), // (labels, start_index, items) - items before start are always shown + // App (PEMenu) + OpenTerminal, // Screens DismissPopup, DisplayPopup(Type, String), diff --git a/deja_vu/src/cli.rs b/core/src/cli.rs similarity index 85% rename from deja_vu/src/cli.rs rename to core/src/cli.rs index 8d886d2..be4d597 100644 --- a/deja_vu/src/cli.rs +++ b/core/src/cli.rs @@ -13,17 +13,13 @@ // You should have received a copy of the GNU General Public License // along with Deja-vu. If not, see . // -use clap::{Parser, Subcommand}; +use clap::Parser; use crate::config::{get_config_dir, get_data_dir}; #[derive(Parser, Debug)] #[command(author, version = version(), about)] pub struct Cli { - /// App mode - #[command(subcommand)] - pub cli_cmd: Command, - /// Tick rate, i.e. number of ticks per second #[arg(short, long, value_name = "FLOAT", default_value_t = 4.0)] pub tick_rate: f64, @@ -33,15 +29,6 @@ pub struct Cli { pub frame_rate: f64, } -#[derive(Clone, Copy, Debug, Subcommand)] -pub enum Command { - /// Clone Windows from one disk to another - Clone, - - /// Diagnose Windows boot issues - Diagnose, -} - const VERSION_MESSAGE: &str = concat!( env!("CARGO_PKG_VERSION"), "-", diff --git a/deja_vu/src/components.rs b/core/src/components.rs similarity index 100% rename from deja_vu/src/components.rs rename to core/src/components.rs diff --git a/deja_vu/src/components/footer.rs b/core/src/components/footer.rs similarity index 59% rename from deja_vu/src/components/footer.rs rename to core/src/components/footer.rs index 6b92fac..7355961 100644 --- a/deja_vu/src/components/footer.rs +++ b/core/src/components/footer.rs @@ -21,7 +21,7 @@ use ratatui::{ use tokio::sync::mpsc::UnboundedSender; use super::Component; -use crate::{action::Action, app::Mode, config::Config}; +use crate::{action::Action, config::Config}; #[derive(Default)] pub struct Footer { @@ -51,33 +51,9 @@ impl Component for Footer { } fn update(&mut self, action: Action) -> Result> { - if let Action::SetMode(new_mode) = action { - self.text = match new_mode { - // Clone modes - Mode::ScanDisks | Mode::PreClone | Mode::Clone | Mode::PostClone => { - String::from("(q) to quit") - } - Mode::SelectParts => { - String::from("(Enter) to select / (s) to start over / (q) to quit") - } - Mode::SelectDisks => String::from( - "(Enter) to select / / (i) to install driver / (r) to rescan / (q) to quit", - ), - Mode::SelectTableType => { - String::from("(Enter) to select / (b) to go back / (q) to quit") - } - Mode::Confirm => String::from("(Enter) to confirm / (b) to go back / (q) to quit"), - Mode::Done | Mode::Failed | Mode::InstallDrivers => { - String::from("(Enter) or (q) to quit") - } - - // Diagnostic modes - Mode::DiagMenu - | Mode::BootDiags - | Mode::BootSetup - | Mode::InjectDrivers - | Mode::ToggleSafeBoot => String::from("(q) to quit"), - } + match action { + Action::UpdateFooter(text) => self.text = text, + _ => {} } Ok(None) } diff --git a/deja_vu/src/components/fps.rs b/core/src/components/fps.rs similarity index 100% rename from deja_vu/src/components/fps.rs rename to core/src/components/fps.rs diff --git a/core/src/components/left.rs b/core/src/components/left.rs new file mode 100644 index 0000000..0f1c833 --- /dev/null +++ b/core/src/components/left.rs @@ -0,0 +1,208 @@ +// This file is part of Deja-vu. +// +// Deja-vu is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Deja-vu is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Deja-vu. If not, see . +// +use color_eyre::Result; +use crossterm::event::KeyEvent; +use ratatui::{ + prelude::*, + widgets::{Block, Borders, HighlightSpacing, List, ListItem, Padding, Paragraph}, +}; +use tokio::sync::mpsc::UnboundedSender; + +use super::{state::StatefulList, Component}; +use crate::{action::Action, config::Config}; + +#[derive(Default)] +pub struct Left { + command_tx: Option>, + config: Config, + labels: Vec, + list: StatefulList, + select_num: usize, + selections: Vec>, + selections_saved: Vec>, + title_text: String, +} + +impl Left { + pub fn new() -> Self { + Self { + select_num: 0, + labels: vec![String::from("one"), String::from("two")], + selections: vec![None, None], + selections_saved: vec![None, None], + title_text: String::from("Home"), + ..Default::default() + } + } + + fn set_highlight(&mut self, index: usize) { + self.list.select(index); + } +} + +impl Component for Left { + fn handle_key_event(&mut self, key: KeyEvent) -> Result> { + let _ = key; // to appease clippy + Ok(None) + } + + fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { + self.command_tx = Some(tx); + Ok(()) + } + + fn register_config_handler(&mut self, config: Config) -> Result<()> { + self.config = config; + Ok(()) + } + + fn update(&mut self, action: Action) -> Result> { + match action { + Action::Highlight(index) => self.set_highlight(index), + Action::KeyUp => self.list.previous(), + Action::KeyDown => self.list.next(), + Action::Process => { + if self.select_num == 0 { + // Selections aren't being used so this is a no-op + } else if let Some(command_tx) = self.command_tx.clone() { + match (self.selections[0], self.selections[1]) { + (None, None) => { + // Making first selection + command_tx.send(Action::Select(self.list.selected(), None))?; + if self.select_num == 1 { + // Confirm selection + command_tx.send(Action::NextScreen)?; + } + } + (Some(first_index), None) => { + if let Some(second_index) = self.list.selected() { + // Making second selection + if first_index == second_index { + // Toggle first selection + command_tx.send(Action::Select(None, None))?; + } else { + // Both selections made, proceed to next screen + command_tx.send(Action::Select( + Some(first_index), + Some(second_index), + ))?; + command_tx.send(Action::NextScreen)?; + } + } + } + _ => panic!("This shouldn't happen?"), + } + } + } + Action::Select(one, two) => { + self.selections[0] = one; + self.selections[1] = two; + self.selections_saved[0] = one; + self.selections_saved[1] = two; + } + Action::SetMode(_) => { + self.selections[0] = None; + self.selections[1] = None; + } + Action::UpdateLeft(title, labels, items, select_num) => { + self.title_text = title; + self.labels = labels + .iter() + .map(|label| format!(" ~{}~", label.to_lowercase())) + .collect(); + self.list.set_items(items); + self.select_num = select_num; + } + _ => {} + } + Ok(None) + } + + fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { + let [title_area, body_area] = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Length(1), Constraint::Min(1)]) + .areas(area); + + // Title + let title_text = if self.selections[1].is_some() || self.select_num == 1 { + "Confirm Selections" + } else { + self.title_text.as_str() + }; + let title = + Paragraph::new(Line::from(Span::styled(title_text, Style::default())).centered()) + .block(Block::default().borders(Borders::NONE)); + frame.render_widget(title, title_area); + + // Body (Blank) + if self.list.is_empty() { + // Leave blank + let paragraph = Paragraph::new(String::new()).block( + Block::default() + .borders(Borders::ALL) + .padding(Padding::new(1, 1, 1, 1)), + ); + frame.render_widget(paragraph, body_area); + + // Bail early + return Ok(()); + } + + // Body + let list_items: Vec = self + .list + .items + .iter() + .enumerate() + .map(|(index, item)| { + let mut style = Style::default(); + let text = if self.selections[0].is_some_and(|first_index| first_index == index) { + if let Some(label) = self.labels.get(0) { + style = style.yellow(); + label.as_str() + } else { + "" + } + } else if self.selections[1].is_some_and(|second_index| second_index == index) { + if let Some(label) = self.labels.get(1) { + style = style.yellow(); + label.as_str() + } else { + "" + } + } else { + "" + }; + ListItem::new(format!(" {item}\n{text}\n\n")).style(style) + }) + .collect(); + let list = List::new(list_items) + .block( + Block::default() + .borders(Borders::ALL) + .padding(Padding::new(1, 1, 1, 1)), + ) + .highlight_spacing(HighlightSpacing::Always) + .highlight_style(Style::new().green().bold()) + .highlight_symbol(" --> ") + .repeat_highlight_symbol(false); + frame.render_stateful_widget(list, body_area, &mut self.list.state); + + // Done + Ok(()) + } +} diff --git a/deja_vu/src/components/popup.rs b/core/src/components/popup.rs similarity index 86% rename from deja_vu/src/components/popup.rs rename to core/src/components/popup.rs index 545f803..c2c0980 100644 --- a/deja_vu/src/components/popup.rs +++ b/core/src/components/popup.rs @@ -23,7 +23,7 @@ use strum::Display; use tokio::sync::mpsc::UnboundedSender; use super::Component; -use crate::{action::Action, app::Mode, config::Config}; +use crate::{action::Action, config::Config}; #[derive(Default, Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)] pub enum Type { @@ -37,17 +37,13 @@ pub enum Type { pub struct Popup { command_tx: Option>, config: Config, - mode: Mode, popup_type: Type, popup_text: String, } impl Popup { pub fn new() -> Self { - Self { - popup_text: String::new(), - ..Default::default() - } + Self::default() } } @@ -67,13 +63,7 @@ impl Component for Popup { Action::DismissPopup => self.popup_text.clear(), Action::DisplayPopup(new_type, new_text) => { self.popup_type = new_type; - self.popup_text = new_text; - } - Action::SetMode(mode) => { - if mode == Mode::ScanDisks { - self.popup_text = String::from("Scanning Disks..."); - } - self.mode = mode; + self.popup_text = format!("\n{new_text}"); } _ => {} } diff --git a/core/src/components/right.rs b/core/src/components/right.rs new file mode 100644 index 0000000..569d0a1 --- /dev/null +++ b/core/src/components/right.rs @@ -0,0 +1,198 @@ +// This file is part of Deja-vu. +// +// Deja-vu is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Deja-vu is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Deja-vu. If not, see . +// +use color_eyre::Result; +use crossterm::event::KeyEvent; +use ratatui::{ + prelude::*, + widgets::{Block, Borders, Padding, Paragraph, Wrap}, +}; +use tokio::sync::mpsc::UnboundedSender; + +use super::{state::StatefulList, Component}; +use crate::{action::Action, config::Config, line::DVLine}; + +#[derive(Default)] +pub struct Right { + command_tx: Option>, + config: Config, + list_header: Vec, + list_labels: Vec>, + list: StatefulList>, + selections: Vec>, + selections_saved: Vec>, + title: String, +} + +impl Right { + pub fn new() -> Self { + Self { + selections: vec![None, None], + selections_saved: vec![None, None], + title: String::from("Info"), + ..Default::default() + } + } + + fn get_first(&self) -> Option { + if self.selections_saved[0].is_some() { + self.selections_saved[0] + } else if self.selections[0].is_some() { + self.selections[0] + } else { + self.list.selected() + } + } + + fn get_second(&self) -> Option { + if self.selections_saved[1].is_some() { + self.selections_saved[1] + } else if self.selections[1].is_some() { + self.selections[1] + } else if self.selections[0].is_some() && self.selections[0] != self.list.selected() { + self.list.selected() + } else { + None + } + } + + fn set_highlight(&mut self, index: usize) { + self.list.select(index); + } +} + +impl Component for Right { + fn handle_key_event(&mut self, key: KeyEvent) -> Result> { + let _ = key; // to appease clippy + Ok(None) + } + + fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { + self.command_tx = Some(tx); + Ok(()) + } + + fn register_config_handler(&mut self, config: Config) -> Result<()> { + self.config = config; + Ok(()) + } + + fn update(&mut self, action: Action) -> Result> { + match action { + Action::Highlight(index) => self.set_highlight(index), + Action::KeyUp => self.list.previous(), + Action::KeyDown => self.list.next(), + Action::Select(one, two) => { + self.selections[0] = one; + self.selections[1] = two; + if self.selections_saved[1].is_none() { + // Update selections_saved + // Conversely, if both selections set then preserve previous choices + self.selections_saved[0] = one; + self.selections_saved[1] = two; + } + } + Action::SelectRight(one, two) => { + self.selections_saved[0] = one; + self.selections_saved[1] = two; + } + Action::SetMode(_) => { + self.selections[0] = None; + self.selections[1] = None; + self.selections_saved[0] = None; + self.selections_saved[1] = None; + } + Action::UpdateRight(labels, start_index, list) => { + self.list_header = list[..start_index].iter().flatten().cloned().collect(); + self.list_labels = labels; + self.list.set_items(list[start_index..].to_vec()); + } + _ => {} + } + Ok(None) + } + + fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { + let [title_area, body_area] = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Length(1), Constraint::Min(1)]) + .areas(area); + + // Title + let title = Paragraph::new(Line::from(self.title.as_str()).centered()) + .block(Block::default().borders(Borders::NONE)); + frame.render_widget(title, title_area); + + // Body + let mut body_text: Vec = Vec::new(); + if !self.list_header.is_empty() { + // Static Header + self.list_header + .iter() + .for_each(|dv| body_text.push(dv.as_line())); + body_text.push(Line::from("")); + body_text.push(Line::from(str::repeat("─", (body_area.width - 4) as usize))); + body_text.push(Line::from("")); + } + + // First selection + if let Some(first_index) = self.get_first() { + if let Some(first_desc) = self.list.get(first_index) { + if let Some(label) = self.list_labels.get(0) { + label + .iter() + .for_each(|dv| body_text.push(dv.as_line().bold())); + body_text.push(Line::from("")); + } + first_desc + .iter() + .for_each(|dv| body_text.push(dv.as_line())); + } + } + + // Second selection + if let Some(second_index) = self.get_second() { + if let Some(second_desc) = self.list.get(second_index) { + // Divider + body_text.push(Line::from("")); + body_text.push(Line::from(str::repeat("─", (body_area.width - 4) as usize))); + body_text.push(Line::from("")); + if let Some(label) = self.list_labels.get(1) { + label + .iter() + .for_each(|dv| body_text.push(dv.as_line().bold())); + } + body_text.push(Line::from("")); + second_desc + .iter() + .for_each(|dv| body_text.push(dv.as_line())); + } + } + + // Build Paragraph + let body = Paragraph::new(body_text) + .style(Style::default().fg(Color::Gray)) + .wrap(Wrap { trim: false }) + .block( + Block::default() + .borders(Borders::ALL) + .padding(Padding::new(1, 1, 1, 1)), + ); + frame.render_widget(body, body_area); + + // Done + Ok(()) + } +} diff --git a/deja_vu/src/components/state.rs b/core/src/components/state.rs similarity index 100% rename from deja_vu/src/components/state.rs rename to core/src/components/state.rs diff --git a/deja_vu/src/components/title.rs b/core/src/components/title.rs similarity index 72% rename from deja_vu/src/components/title.rs rename to core/src/components/title.rs index 9d89163..ee4f088 100644 --- a/deja_vu/src/components/title.rs +++ b/core/src/components/title.rs @@ -21,23 +21,19 @@ use ratatui::{ use tokio::sync::mpsc::UnboundedSender; use super::Component; -use crate::{action::Action, cli, config::Config}; +use crate::{action::Action, config::Config}; #[derive(Default)] pub struct Title { command_tx: Option>, config: Config, - title_text: String, + text: String, } impl Title { - pub fn new(cli_cmd: cli::Command) -> Self { - let title_text = match cli_cmd { - cli::Command::Clone => String::from("Deja-Vu: Clone"), - cli::Command::Diagnose => String::from("Deja-Vu: Diagnostics"), - }; + pub fn new(text: &str) -> Self { Self { - title_text, + text: String::from(text), ..Default::default() } } @@ -54,16 +50,19 @@ impl Component for Title { Ok(()) } - // fn update(&mut self, action: Action) -> Result> { - // match action { - // _ => {} - // } - // Ok(None) - // } + fn update(&mut self, action: Action) -> Result> { + match action { + _ => {} + } + Ok(None) + } fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { // Title Block - let title_text = Span::styled(&self.title_text, Style::default().fg(Color::LightCyan)); + let title_text = Span::styled( + format!("{}: {}", self.config.app_title.as_str(), self.text), + Style::default().fg(Color::LightCyan), + ); let title = Paragraph::new(Line::from(title_text).centered()) .block(Block::default().borders(Borders::ALL)); frame.render_widget(title, area); diff --git a/deja_vu/src/config.rs b/core/src/config.rs similarity index 95% rename from deja_vu/src/config.rs rename to core/src/config.rs index f21ca83..49eb824 100644 --- a/deja_vu/src/config.rs +++ b/core/src/config.rs @@ -26,9 +26,9 @@ use ratatui::style::{Color, Modifier, Style}; use serde::{de::Deserializer, Deserialize}; use tracing::error; -use crate::{action::Action, app::Mode}; +use crate::{action::Action, state::Mode}; -const CONFIG: &str = include_str!("../config/config.json5"); +const CONFIG: &str = include_str!("../../config/config.json5"); #[derive(Clone, Debug, Deserialize, Default)] pub struct AppConfig { @@ -40,8 +40,12 @@ pub struct AppConfig { #[derive(Clone, Debug, Default, Deserialize)] pub struct Config { + #[serde(default)] + pub app_title: String, #[serde(default)] pub clone_app_path: PathBuf, + #[serde(default)] + pub conemu_path: PathBuf, #[serde(default, flatten)] pub config: AppConfig, #[serde(default)] @@ -50,14 +54,15 @@ pub struct Config { pub styles: Styles, } +pub static PROJECT_NAME: &'static str = "DEJA-VU"; lazy_static! { - pub static ref PROJECT_NAME: String = env!("CARGO_CRATE_NAME").to_uppercase().to_string(); + //pub static ref PROJECT_NAME: String = env!("CARGO_PKG_NAME").to_uppercase().to_string(); pub static ref DATA_FOLDER: Option = - env::var(format!("{}_DATA", PROJECT_NAME.clone())) + env::var(format!("{}_DATA", PROJECT_NAME)) .ok() .map(PathBuf::from); pub static ref CONFIG_FOLDER: Option = - env::var(format!("{}_CONFIG", PROJECT_NAME.clone())) + env::var(format!("{}_CONFIG", PROJECT_NAME)) .ok() .map(PathBuf::from); } @@ -68,12 +73,17 @@ impl Config { let data_dir = get_data_dir(); let config_dir = get_config_dir(); let mut builder = config::Config::builder() - .set_default("data_dir", data_dir.to_str().unwrap())? - .set_default("config_dir", config_dir.to_str().unwrap())? + .set_default("app_title", default_config.app_title.as_str())? .set_default( "clone_app_path", String::from("C:\\Program Files\\Some Clone Tool\\app.exe"), - )?; + )? + .set_default( + "conemu_path", + String::from("C:\\Program Files\\ConEmu\\ConEmu64.exe"), + )? + .set_default("config_dir", config_dir.to_str().unwrap())? + .set_default("data_dir", data_dir.to_str().unwrap())?; let config_files = [ ("config.json5", config::FileFormat::Json5), @@ -95,7 +105,6 @@ impl Config { if !found_config { error!("No configuration file found. Application may not behave as expected"); } - let mut cfg: Self = builder.build()?.try_deserialize()?; for (mode, default_bindings) in default_config.keybindings.iter() { @@ -140,7 +149,8 @@ pub fn get_config_dir() -> PathBuf { } fn project_directory() -> Option { - ProjectDirs::from("com", "WizardKit", env!("CARGO_PKG_NAME")) + ProjectDirs::from("com", "Deja-vu", "deja-vu") + //ProjectDirs::from("com", "Deja-vu", env!("CARGO_PKG_NAME")) } #[derive(Clone, Debug, Default, Deref, DerefMut)] @@ -525,7 +535,7 @@ mod tests { let c = Config::new()?; assert_eq!( c.keybindings - .get(&Mode::ScanDisks) // i.e. Home + .get(&Mode::Home) // i.e. Home .unwrap() .get(&parse_key_sequence("").unwrap_or_default()) .unwrap(), diff --git a/deja_vu/src/errors.rs b/core/src/errors.rs similarity index 100% rename from deja_vu/src/errors.rs rename to core/src/errors.rs diff --git a/pe_menu/src/lib.rs b/core/src/lib.rs similarity index 79% rename from pe_menu/src/lib.rs rename to core/src/lib.rs index 02aac4f..2e49e5e 100644 --- a/pe_menu/src/lib.rs +++ b/core/src/lib.rs @@ -13,17 +13,14 @@ // You should have received a copy of the GNU General Public License // along with Deja-vu. If not, see . // -/// Application. -pub mod app; - -/// Terminal events handler. -pub mod event; - -/// Widget renderer. -pub mod ui; - -/// Terminal user interface. +pub mod action; +pub mod cli; +pub mod components; +pub mod config; +pub mod errors; +pub mod line; +pub mod logging; +pub mod state; +pub mod system; +pub mod tasks; pub mod tui; - -/// Event handler. -pub mod handler; diff --git a/core/src/line.rs b/core/src/line.rs new file mode 100644 index 0000000..038a848 --- /dev/null +++ b/core/src/line.rs @@ -0,0 +1,94 @@ +// This file is part of Deja-vu. +// +// Deja-vu is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Deja-vu is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Deja-vu. If not, see . +// +use ratatui::{ + style::{Color, Style}, + text::{Line, Span}, +}; +use serde::{Deserialize, Serialize}; +use std::iter::zip; + +use crate::system::disk::{Disk, Partition}; + +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct DVLine { + pub line_parts: Vec, + pub line_colors: Vec, +} + +impl DVLine { + /// Convert to Line with colored span(s) + pub fn as_line(&self) -> Line { + let mut spans = Vec::new(); + zip(self.line_parts.clone(), self.line_colors.clone()) + .for_each(|(part, color)| spans.push(Span::styled(part, Style::default().fg(color)))); + Line::from(spans) + } + + pub fn blank() -> Self { + Self { + line_parts: vec![String::new()], + line_colors: vec![Color::Reset], + } + } +} + +pub fn get_disk_description_right(disk: &Disk) -> Vec { + let mut description: Vec = vec![ + DVLine { + line_parts: vec![format!( + "{:<8} {:>11} {:<4} {:<4} {}", + "Disk ID", "Size", "Conn", "Type", "Model (Serial)" + )], + line_colors: vec![Color::Green], + }, + DVLine { + line_parts: vec![disk.description.clone()], + line_colors: vec![Color::Reset], + }, + DVLine::blank(), + DVLine { + line_parts: vec![format!( + "{:<8} {:>11} {:<7} {}", + "Part ID", "Size", "(FS)", "\"Label\"" + )], + line_colors: vec![Color::Blue], + }, + ]; + for line in &disk.parts_description { + description.push(DVLine { + line_parts: vec![line.clone()], + line_colors: vec![Color::Reset], + }); + } + description +} + +pub fn get_part_description(part: &Partition) -> Vec { + let description: Vec = vec![ + DVLine { + line_parts: vec![format!( + "{:<8} {:>11} {:<7} {}", + "Part ID", "Size", "(FS)", "\"Label\"" + )], + line_colors: vec![Color::Blue], + }, + DVLine { + line_parts: vec![format!("{part}")], + line_colors: vec![Color::Reset], + }, + ]; + description +} diff --git a/deja_vu/src/logging.rs b/core/src/logging.rs similarity index 90% rename from deja_vu/src/logging.rs rename to core/src/logging.rs index 2494013..741e981 100644 --- a/deja_vu/src/logging.rs +++ b/core/src/logging.rs @@ -20,8 +20,9 @@ use tracing_subscriber::{fmt, prelude::*, EnvFilter}; use crate::config; lazy_static::lazy_static! { - pub static ref LOG_ENV: String = format!("{}_LOG_LEVEL", config::PROJECT_NAME.clone()); - pub static ref LOG_FILE: String = format!("{}.log", env!("CARGO_PKG_NAME")); + pub static ref LOG_ENV: String = format!("{}_LOG_LEVEL", config::PROJECT_NAME); + pub static ref LOG_FILE: String = format!("{}.log", config::PROJECT_NAME.to_lowercase()); + //pub static ref LOG_FILE: String = format!("{}.log", env!("CARGO_PKG_NAME")); } pub fn init() -> Result<()> { diff --git a/core/src/state.rs b/core/src/state.rs new file mode 100644 index 0000000..fab9ede --- /dev/null +++ b/core/src/state.rs @@ -0,0 +1,70 @@ +// This file is part of Deja-vu. +// +// Deja-vu is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Deja-vu is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Deja-vu. If not, see . +// + +use std::sync::{Arc, Mutex}; + +use serde::{Deserialize, Serialize}; + +use crate::system::{ + disk::{Disk, PartitionTableType}, + drivers, +}; + +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum Mode { + // Core + #[default] + Home, + Done, + Failed, + // Clone + ScanDisks, + InstallDrivers, + SelectDisks, + SelectTableType, + Confirm, + PreClone, + Clone, + SelectParts, + PostClone, + // WinPE + PEMenu, +} + +#[derive(Debug, Default)] +pub struct CloneSettings { + pub disk_index_dest: Option, + pub disk_index_source: Option, + pub disk_list: Arc>>, + pub driver_list: Vec, + pub part_index_boot: Option, + pub part_index_os: Option, + pub driver: Option, + pub table_type: Option, +} + +impl CloneSettings { + pub fn new(disk_list: Arc>>) -> Self { + CloneSettings { + disk_list, + ..Default::default() + } + } + + pub fn scan_drivers(&mut self) { + self.driver_list = drivers::scan(); + } +} diff --git a/deja_vu/src/system.rs b/core/src/system.rs similarity index 100% rename from deja_vu/src/system.rs rename to core/src/system.rs diff --git a/deja_vu/src/system/boot.rs b/core/src/system/boot.rs similarity index 100% rename from deja_vu/src/system/boot.rs rename to core/src/system/boot.rs diff --git a/deja_vu/src/system/cpu.rs b/core/src/system/cpu.rs similarity index 100% rename from deja_vu/src/system/cpu.rs rename to core/src/system/cpu.rs diff --git a/deja_vu/src/system/disk.rs b/core/src/system/disk.rs similarity index 100% rename from deja_vu/src/system/disk.rs rename to core/src/system/disk.rs diff --git a/deja_vu/src/system/diskpart.rs b/core/src/system/diskpart.rs similarity index 100% rename from deja_vu/src/system/diskpart.rs rename to core/src/system/diskpart.rs diff --git a/deja_vu/src/system/drivers.rs b/core/src/system/drivers.rs similarity index 100% rename from deja_vu/src/system/drivers.rs rename to core/src/system/drivers.rs diff --git a/deja_vu/src/tasks.rs b/core/src/tasks.rs similarity index 100% rename from deja_vu/src/tasks.rs rename to core/src/tasks.rs diff --git a/deja_vu/src/tests/mod.rs b/core/src/tests/mod.rs similarity index 100% rename from deja_vu/src/tests/mod.rs rename to core/src/tests/mod.rs diff --git a/deja_vu/src/tests/sample_output.rs b/core/src/tests/sample_output.rs similarity index 100% rename from deja_vu/src/tests/sample_output.rs rename to core/src/tests/sample_output.rs diff --git a/deja_vu/src/tui.rs b/core/src/tui.rs similarity index 100% rename from deja_vu/src/tui.rs rename to core/src/tui.rs diff --git a/deja_vu/.envrc b/deja_vu/.envrc deleted file mode 100644 index dd7b181..0000000 --- a/deja_vu/.envrc +++ /dev/null @@ -1,3 +0,0 @@ -export DEJA_VU_CONFIG=`pwd`/config -export DEJA_VU_DATA=`pwd`/data -export DEJA_VU_LOG_LEVEL=debug diff --git a/deja_vu/Cargo.toml b/deja_vu/Cargo.toml index 2a1c29a..7756600 100644 --- a/deja_vu/Cargo.toml +++ b/deja_vu/Cargo.toml @@ -16,51 +16,30 @@ [package] name = "deja-vu" authors = ["2Shirt <2xShirt@gmail.com>"] -description = "Clone Windows." edition = "2021" license = "GPL" version = "0.2.0" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] -better-panic = "0.3.0" -clap = { version = "4.4.5", features = [ - "derive", - "cargo", - "wrap_help", - "unicode", - "string", - "unstable-styles", -] } +core = { path = "../core" } color-eyre = "0.6.3" -config = "0.14.0" -crossterm = { version = "0.28.1", features = ["serde", "event-stream"] } -derive_deref = "1.1.1" -directories = "5.0.1" -futures = "0.3.30" -human-panic = "2.0.1" -json5 = "0.4.1" -lazy_static = "1.5.0" -libc = "0.2.158" -once_cell = "1.20.2" -pretty_assertions = "1.4.0" -ratatui = { version = "0.28.1", features = ["serde", "macros"] } -raw-cpuid = "11.2.0" -regex = "1.11.1" -serde = { version = "1.0.208", features = ["derive"] } +clap = { version = "4.4.5", features = [ + "derive", + "cargo", + "wrap_help", + "unicode", + "string", + "unstable-styles", + ] } +ratatui = { version = "0.29.0", features = ["serde", "macros"] } +serde = { version = "1.0.217", features = ["derive"] } serde_json = "1.0.125" -signal-hook = "0.3.17" -strip-ansi-escapes = "0.2.0" -strum = { version = "0.26.3", features = ["derive"] } -tempfile = "3.13.0" -tokio = { version = "1.39.3", features = ["full"] } +tokio = { version = "1.43.0", features = ["full"] } tokio-util = "0.7.11" -tracing = "0.1.40" +tracing = "0.1.41" tracing-error = "0.2.0" tracing-subscriber = { version = "0.3.18", features = ["env-filter", "serde"] } -walkdir = "2.5.0" +rand = "0.8.5" [build-dependencies] anyhow = "1.0.86" diff --git a/deja_vu/config/config.json5 b/deja_vu/config/config.json5 deleted file mode 100644 index 2bf9438..0000000 --- a/deja_vu/config/config.json5 +++ /dev/null @@ -1,130 +0,0 @@ -{ - "clone_app_path": "C:/Program Files/Some Clone Tool/app.exe", - "keybindings": { - "ScanDisks": { - "": "Quit", // Quit the application - "": "Quit", // Another way to quit - "": "Quit", // Yet another way to quit - "": "Suspend" // Suspend the application - }, - "InstallDrivers": { - "": "Process", - "": "KeyUp", - "": "KeyDown", - "": "Quit", // Quit the application - "": "Quit", // Another way to quit - "": "Quit", // Yet another way to quit - "": "Suspend" // Suspend the application - }, - "SelectDisks": { - "": "InstallDriver", - "": "ScanDisks", - "": "Process", - "": "KeyUp", - "": "KeyDown", - "": "Quit", // Quit the application - "": "Quit", // Another way to quit - "": "Quit", // Yet another way to quit - "": "Suspend" // Suspend the application - }, - "SelectTableType": { - "": "PrevScreen", - "": "Process", - "": "KeyUp", - "": "KeyDown", - "": "Quit", // Quit the application - "": "Quit", // Another way to quit - "": "Quit", // Yet another way to quit - "": "Suspend" // Suspend the application - }, - "Confirm": { - "": "PrevScreen", - "": "Process", - "": "Quit", // Quit the application - "": "Quit", // Another way to quit - "": "Quit", // Yet another way to quit - "": "Suspend" // Suspend the application - }, - "PreClone": { - "": "Quit", // Quit the application - "": "Quit", // Another way to quit - "": "Quit", // Yet another way to quit - "": "Suspend" // Suspend the application - }, - "Clone": { - "": "Quit", // Quit the application - "": "Quit", // Another way to quit - "": "Quit", // Yet another way to quit - "": "Suspend" // Suspend the application - }, - "SelectParts": { - "": "Process", - "": "KeyUp", - "": "KeyDown", - "": "ScanDisks", // Start over - "": "Quit", // Quit the application - "": "Quit", // Another way to quit - "": "Quit", // Yet another way to quit - "": "Suspend" // Suspend the application - }, - "PostClone": { - "": "Quit", // Quit the application - "": "Quit", // Another way to quit - "": "Quit", // Yet another way to quit - "": "Suspend" // Suspend the application - }, - "Done": { - "": "Quit", - "": "Quit", // Quit the application - "": "Quit", // Another way to quit - "": "Quit", // Yet another way to quit - "": "Suspend" // Suspend the application - }, - "Failed": { - "": "Quit", - "": "Quit", // Quit the application - "": "Quit", // Another way to quit - "": "Quit", // Yet another way to quit - "": "Suspend" // Suspend the application - }, - // Diagnostic modes - "DiagMenu": { - "": "Process", - "": "KeyUp", - "": "KeyDown", - "": "InstallDriver", - "": "Quit", // Quit the application - "": "Quit", // Another way to quit - "": "Quit", // Yet another way to quit - "": "Suspend" // Suspend the application - }, - "BootDiags": { - "": "Process", - "": "Quit", // Quit the application - "": "Quit", // Another way to quit - "": "Quit", // Yet another way to quit - "": "Suspend" // Suspend the application - }, - "BootSetup": { - "": "Process", - "": "Quit", // Quit the application - "": "Quit", // Another way to quit - "": "Quit", // Yet another way to quit - "": "Suspend" // Suspend the application - }, - "InjectDrivers": { - "": "Process", - "": "Quit", // Quit the application - "": "Quit", // Another way to quit - "": "Quit", // Yet another way to quit - "": "Suspend" // Suspend the application - }, - "ToggleSafeBoot": { - "": "Process", - "": "Quit", // Quit the application - "": "Quit", // Another way to quit - "": "Quit", // Yet another way to quit - "": "Suspend" // Suspend the application - }, - } -} diff --git a/deja_vu/src/app.rs b/deja_vu/src/app.rs index 6336a39..ae6ef72 100644 --- a/deja_vu/src/app.rs +++ b/deja_vu/src/app.rs @@ -12,7 +12,23 @@ // // You should have received a copy of the GNU General Public License // along with Deja-vu. If not, see . -// + +use core::{ + action::Action, + components::{ + footer::Footer, fps::FpsCounter, left::Left, popup, right::Right, state::StatefulList, + title::Title, Component, + }, + config::Config, + line::{get_disk_description_right, get_part_description, DVLine}, + state::{CloneSettings, Mode}, + system::{ + boot, cpu::get_cpu_name, disk::PartitionTableType, diskpart::build_dest_format_script, + drivers, + }, + tasks::{Task, Tasks}, + tui::{Event, Tui}, +}; use std::{ env, iter::zip, @@ -20,32 +36,16 @@ use std::{ }; use color_eyre::Result; -use crossterm::event::KeyEvent; +use rand::random; use ratatui::{ + crossterm::event::KeyEvent, layout::{Constraint, Direction, Layout}, prelude::Rect, + style::Color, }; -use serde::{Deserialize, Serialize}; use tokio::sync::mpsc; use tracing::{debug, info}; -use crate::{ - action::Action, - cli, - components::{ - footer::Footer, fps::FpsCounter, left::Left, popup, right::Right, title::Title, Component, - }, - config::Config, - system::{ - boot, - disk::{Disk, PartitionTableType}, - diskpart::build_dest_format_script, - drivers::{self, Driver}, - }, - tasks::{Task, Tasks}, - tui::{Event, Tui}, -}; - pub struct App { // TUI action_rx: mpsc::UnboundedReceiver, @@ -58,61 +58,31 @@ pub struct App { should_suspend: bool, tick_rate: f64, // App - cli_cmd: cli::Command, + clone: CloneSettings, cur_mode: Mode, - disk_index_dest: Option, - disk_index_source: Option, - disk_list: Arc>>, - part_index_boot: Option, - part_index_os: Option, - driver: Option, + list: StatefulList, prev_mode: Mode, selections: Vec>, - table_type: Option, tasks: Tasks, } -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum Mode { - #[default] - ScanDisks, - InstallDrivers, - SelectDisks, - SelectTableType, - Confirm, - PreClone, - Clone, - SelectParts, - PostClone, - Done, - Failed, - // Diagnostic modes - DiagMenu, - BootDiags, - BootSetup, - InjectDrivers, - ToggleSafeBoot, -} - impl App { - pub fn new(cli_cmd: cli::Command, tick_rate: f64, frame_rate: f64) -> Result { + pub fn new(tick_rate: f64, frame_rate: f64) -> Result { let (action_tx, action_rx) = mpsc::unbounded_channel(); let disk_list_arc = Arc::new(Mutex::new(Vec::new())); - let mut tasks = Tasks::new(action_tx.clone(), disk_list_arc.clone()); - tasks.add(Task::ScanDisks); + let tasks = Tasks::new(action_tx.clone(), disk_list_arc.clone()); Ok(Self { // TUI action_rx, action_tx, components: vec![ - Box::new(Title::new(cli_cmd)), + Box::new(Title::new("Clone Tool")), Box::new(FpsCounter::new()), Box::new(Left::new()), Box::new(Right::new()), Box::new(Footer::new()), Box::new(popup::Popup::new()), ], - cli_cmd, config: Config::new()?, frame_rate, last_tick_key_events: Vec::new(), @@ -120,73 +90,57 @@ impl App { should_suspend: false, tick_rate, // App - cur_mode: match cli_cmd { - cli::Command::Clone => Mode::ScanDisks, - cli::Command::Diagnose => Mode::DiagMenu, - }, - disk_index_dest: None, - disk_index_source: None, - disk_list: disk_list_arc, - driver: None, - part_index_boot: None, - part_index_os: None, - prev_mode: Mode::ScanDisks, + clone: CloneSettings::new(disk_list_arc), + cur_mode: Mode::default(), + list: StatefulList::default(), + prev_mode: Mode::default(), selections: vec![None, None], - table_type: None, tasks, }) } - pub fn next_mode(&mut self) -> Option { - info!( - "Prev Mode: {:?} // Cur Mode: {:?}", - self.prev_mode, self.cur_mode - ); - let new_mode = match (self.prev_mode, self.cur_mode) { - // Clone states - (_, Mode::InstallDrivers) => self.prev_mode, - (_, Mode::ScanDisks) => Mode::SelectDisks, - (_, Mode::SelectDisks | Mode::SelectTableType | Mode::SelectParts) => { - if self.selections[1].is_some() { - Mode::Confirm - } else { - self.cur_mode - } - } - (Mode::SelectDisks, Mode::Confirm) => Mode::SelectTableType, - (Mode::SelectTableType, Mode::Confirm) => Mode::PreClone, - (_, Mode::PreClone) => Mode::Clone, - (_, Mode::Clone) => Mode::SelectParts, - (Mode::SelectParts, Mode::Confirm) => Mode::PostClone, - (_, Mode::PostClone | Mode::Done) => Mode::Done, - (_, Mode::Failed) => Mode::Failed, - - // Diagnostic states - ( - _, - Mode::DiagMenu - | Mode::BootDiags - | Mode::BootSetup - | Mode::InjectDrivers - | Mode::ToggleSafeBoot, - ) => Mode::DiagMenu, - + pub fn prev_mode(&mut self) -> Option { + let new_mode = match self.cur_mode { + Mode::Home => Some(Mode::Home), + Mode::Failed => Some(Mode::Failed), + Mode::Done => Some(Mode::Done), + Mode::SelectParts => Some(Mode::SelectParts), + Mode::Confirm => Some(Mode::SelectTableType), + Mode::SelectTableType => Some(Mode::SelectDisks), + Mode::SelectDisks => Some(Mode::ScanDisks), + // Disallowed moves + Mode::InstallDrivers + | Mode::ScanDisks + | Mode::PreClone + | Mode::Clone + | Mode::PostClone => None, // Invalid states - (_, Mode::Confirm) => panic!("This shouldn't happen."), + Mode::PEMenu => panic!("This shouldn't happen?"), + }; + new_mode + } + + pub fn next_mode(&mut self) -> Option { + let new_mode = match self.cur_mode { + Mode::Home => Mode::ScanDisks, + Mode::InstallDrivers => Mode::ScanDisks, + Mode::ScanDisks => Mode::SelectDisks, + Mode::SelectDisks => Mode::SelectTableType, + Mode::SelectTableType => Mode::Confirm, + Mode::Confirm => Mode::PreClone, + Mode::PreClone => Mode::Clone, + Mode::Clone => Mode::SelectParts, + Mode::SelectParts => Mode::PostClone, + Mode::PostClone | Mode::Done => Mode::Done, + Mode::Failed => Mode::Failed, + // Invalid states + Mode::PEMenu => panic!("This shouldn't happen?"), }; if new_mode == self.cur_mode { + // No mode change needed None } else { - match self.cur_mode { - // Update prev_mode if appropriate - Mode::Confirm => {} - Mode::PreClone | Mode::Clone | Mode::PostClone | Mode::Done => { - // Override since we're past the point of no return - self.prev_mode = self.cur_mode; - } - _ => self.prev_mode = self.cur_mode, - } Some(new_mode) } } @@ -195,11 +149,16 @@ impl App { info!("Setting mode to {new_mode:?}"); self.cur_mode = new_mode; match new_mode { + Mode::InstallDrivers => self.clone.scan_drivers(), Mode::ScanDisks => { self.prev_mode = self.cur_mode; if self.tasks.idle() { self.tasks.add(Task::ScanDisks); } + self.action_tx.send(Action::DisplayPopup( + popup::Type::Info, + String::from("Scanning Disks..."), + ))?; } Mode::PreClone => { self.action_tx.send(Action::DisplayPopup( @@ -208,10 +167,10 @@ impl App { ))?; // Build Diskpart script to format destination disk - let disk_list = self.disk_list.lock().unwrap(); - if let Some(disk_index) = self.disk_index_dest { + let disk_list = self.clone.disk_list.lock().unwrap(); + if let Some(disk_index) = self.clone.disk_index_dest { if let Some(disk) = disk_list.get(disk_index) { - let table_type = self.table_type.clone().unwrap(); + let table_type = self.clone.table_type.clone().unwrap(); let diskpart_script = build_dest_format_script(disk.id, &table_type); self.tasks.add(Task::Diskpart(diskpart_script)); } @@ -226,7 +185,7 @@ impl App { self.config.clone_app_path.clone(), Vec::new(), )); - if let Some(dest_index) = self.disk_index_dest { + if let Some(dest_index) = self.clone.disk_index_dest { self.tasks.add(Task::UpdateDestDisk(dest_index)); } } @@ -251,12 +210,12 @@ impl App { }; // Add actions - let disk_list = self.disk_list.lock().unwrap(); - if let Some(disk_index) = self.disk_index_dest { + let disk_list = self.clone.disk_list.lock().unwrap(); + if let Some(disk_index) = self.clone.disk_index_dest { if let Some(disk) = disk_list.get(disk_index) { - let table_type = self.table_type.clone().unwrap(); - let letter_boot = disk.get_part_letter(self.part_index_boot.unwrap()); - let letter_os = disk.get_part_letter(self.part_index_os.unwrap()); + let table_type = self.clone.table_type.clone().unwrap(); + let letter_boot = disk.get_part_letter(self.clone.part_index_boot.unwrap()); + let letter_os = disk.get_part_letter(self.clone.part_index_os.unwrap()); // Safety check if letter_boot.is_empty() || letter_os.is_empty() { @@ -274,7 +233,7 @@ impl App { } // Inject driver(s) (if selected) - if let Some(driver) = &self.driver { + if let Some(driver) = &self.clone.driver { if let Ok(task) = boot::inject_driver(driver, &letter_os, &system32) { self.tasks.add(task); } else { @@ -288,10 +247,8 @@ impl App { } } Mode::Done => { - self.action_tx.send(Action::DisplayPopup( - popup::Type::Success, - String::from("COMPLETE\n\n\nThank you for using this tool!"), - ))?; + self.action_tx + .send(Action::DisplayPopup(popup::Type::Success, fortune()))?; } _ => {} } @@ -316,17 +273,7 @@ impl App { } let action_tx = self.action_tx.clone(); - // Init based on cli::Command - match self.cli_cmd { - cli::Command::Clone => { - action_tx.send(Action::SetMode(Mode::ScanDisks))?; - } - cli::Command::Diagnose => { - self.prev_mode = Mode::DiagMenu; - action_tx.send(Action::SetMode(Mode::DiagMenu))?; - } - } - + action_tx.send(Action::SetMode(Mode::ScanDisks))?; loop { self.handle_events(&mut tui).await?; self.handle_actions(&mut tui)?; @@ -408,6 +355,8 @@ impl App { Action::Suspend => self.should_suspend = true, Action::Resume => self.should_suspend = false, Action::ClearScreen => tui.terminal.clear()?, + Action::KeyUp => self.list.previous(), + Action::KeyDown => self.list.next(), Action::Error(ref msg) => { self.action_tx .send(Action::DisplayPopup(popup::Type::Error, msg.clone()))?; @@ -416,47 +365,109 @@ impl App { Action::InstallDriver => { self.action_tx.send(Action::SetMode(Mode::InstallDrivers))?; } - Action::SelectDriver(ref driver) => { - self.driver = Some(driver.clone()); - drivers::load(&driver.inf_paths); - self.action_tx.send(Action::NextScreen)?; - } - Action::SelectTableType(ref table_type) => { - self.table_type = Some(table_type.clone()); - self.action_tx.send(Action::NextScreen)?; - } + Action::Process => match self.cur_mode { + Mode::Confirm => { + self.action_tx.send(Action::NextScreen)?; + } + _ => {} + }, Action::Resize(w, h) => self.handle_resize(tui, w, h)?, Action::Render => self.render(tui)?, Action::PrevScreen => { - let new_mode = match (self.prev_mode, self.cur_mode) { - (Mode::SelectTableType, Mode::SelectTableType) => Mode::SelectDisks, - (_, _) => self.prev_mode, - }; - self.action_tx.send(Action::SetMode(new_mode))?; - } - Action::NextScreen => { - if let Some(mode) = self.next_mode() { - self.action_tx.send(Action::DismissPopup)?; - self.action_tx.send(Action::SetMode(mode))?; + if let Some(new_mode) = self.prev_mode() { + self.prev_mode = new_mode; + self.cur_mode = new_mode; + self.action_tx.send(Action::SetMode(new_mode))?; } } + Action::NextScreen => match self.next_mode() { + None => {} + Some(next) => { + self.prev_mode = self.cur_mode; + self.cur_mode = next; + self.action_tx.send(Action::DismissPopup)?; + self.action_tx.send(Action::SetMode(next))?; + } + }, Action::ScanDisks => self.action_tx.send(Action::SetMode(Mode::ScanDisks))?, Action::Select(one, two) => { match self.cur_mode { + Mode::InstallDrivers => { + if let Some(index) = one { + if let Some(driver) = self.clone.driver_list.get(index).cloned() { + drivers::load(&driver.inf_paths); + self.clone.driver = Some(driver); + } + } + } Mode::SelectDisks => { - self.disk_index_source = one; - self.disk_index_dest = two; + self.clone.disk_index_source = one; + self.clone.disk_index_dest = two; } Mode::SelectParts => { - self.part_index_boot = one; - self.part_index_os = two; + self.clone.part_index_boot = one; + self.clone.part_index_os = two; + } + Mode::SelectTableType => { + self.clone.table_type = { + if let Some(index) = one { + match index { + 0 => Some(PartitionTableType::Guid), + 1 => Some(PartitionTableType::Legacy), + index => { + panic!("Failed to select PartitionTableType: {}", index) + } + } + } else { + None + } + } } _ => {} } self.selections[0] = one; self.selections[1] = two; } - Action::SetMode(new_mode) => self.set_mode(new_mode)?, + Action::SetMode(new_mode) => { + // Clear TableType selection + match new_mode { + Mode::SelectDisks | Mode::SelectTableType => { + self.clone.table_type = None; + } + _ => {} + } + self.set_mode(new_mode)?; + self.action_tx + .send(Action::UpdateFooter(build_footer_string(self.cur_mode)))?; + self.action_tx.send(build_left_items(self, self.cur_mode))?; + self.action_tx + .send(build_right_items(self, self.cur_mode))?; + match new_mode { + Mode::SelectTableType | Mode::Confirm => { + // Select source/dest disks + self.action_tx.send(Action::SelectRight( + self.clone.disk_index_source, + self.clone.disk_index_dest, + ))?; + } + Mode::SelectParts => { + // Select first partition as boot partition + self.action_tx.send(Action::Select(Some(0), None))?; + + // Highlight 2nd or 3rd partition as OS partition + let index = if let Some(table_type) = &self.clone.table_type { + match table_type { + PartitionTableType::Guid => 2, + PartitionTableType::Legacy => 1, + } + } else { + 1 + }; + self.action_tx.send(Action::Highlight(index))?; + } + _ => {} + }; + } _ => {} } for component in &mut self.components { @@ -548,3 +559,178 @@ fn get_chunks(r: Rect) -> Vec { // Done chunks } + +fn build_footer_string(cur_mode: Mode) -> String { + match cur_mode { + Mode::Home | Mode::ScanDisks | Mode::PreClone | Mode::Clone | Mode::PostClone => { + String::from("(q) to quit") + } + Mode::SelectParts => String::from("(Enter) to select / (s) to start over / (q) to quit"), + Mode::SelectDisks => String::from( + "(Enter) to select / / (i) to install driver / (r) to rescan / (q) to quit", + ), + Mode::SelectTableType => String::from("(Enter) to select / (b) to go back / (q) to quit"), + Mode::Confirm => String::from("(Enter) to confirm / (b) to go back / (q) to quit"), + Mode::Done | Mode::Failed | Mode::InstallDrivers => String::from("(Enter) or (q) to quit"), + // Invalid states + Mode::PEMenu => panic!("This shouldn't happen?"), + } +} + +fn build_left_items(app: &App, cur_mode: Mode) -> Action { + let select_num: usize; + let title: String; + let mut items = Vec::new(); + let mut labels: Vec = Vec::new(); + match cur_mode { + Mode::Home => { + select_num = 0; + title = String::from("Home"); + } + Mode::InstallDrivers => { + select_num = 1; + title = String::from("Install Drivers"); + app.clone + .driver_list + .iter() + .for_each(|driver| items.push(driver.to_string())); + } + Mode::SelectDisks => { + select_num = 2; + title = String::from("Select Source and Destination Disks"); + labels.push(String::from("source")); + labels.push(String::from("dest")); + let disk_list = app.clone.disk_list.lock().unwrap(); + disk_list + .iter() + .for_each(|disk| items.push(disk.description.to_string())); + } + Mode::SelectTableType => { + select_num = 1; + title = String::from("Select Partition Table Type"); + items.push(format!("{}", PartitionTableType::Guid)); + items.push(format!("{}", PartitionTableType::Legacy)); + } + Mode::Confirm => { + select_num = 0; + title = String::from("Confirm Selections"); + } + Mode::ScanDisks | Mode::PreClone | Mode::Clone | Mode::PostClone => { + select_num = 0; + title = String::from("Processing"); + } + Mode::SelectParts => { + select_num = 2; + title = String::from("Select Boot and OS Partitions"); + labels.push(String::from("boot")); + labels.push(String::from("os")); + let disk_list = app.clone.disk_list.lock().unwrap(); + if let Some(index) = app.clone.disk_index_dest { + if let Some(disk) = disk_list.get(index) { + disk.get_parts().iter().for_each(|part| { + items.push(part.to_string()); + }); + } + } + } + Mode::Done | Mode::Failed => { + select_num = 0; + title = String::from("Done"); + } + // Invalid states + Mode::PEMenu => panic!("This shouldn't happen?"), + }; + Action::UpdateLeft(title, labels, items, select_num) +} + +fn build_right_items(app: &App, cur_mode: Mode) -> Action { + let mut items = Vec::new(); + let mut labels: Vec> = Vec::new(); + let mut start_index = 0; + match cur_mode { + Mode::InstallDrivers => { + items.push(vec![DVLine { + line_parts: vec![String::from("CPU")], + line_colors: vec![Color::Cyan], + }]); + items.push(vec![DVLine { + line_parts: vec![get_cpu_name()], + line_colors: vec![Color::Reset], + }]); + start_index = 2; + } + Mode::SelectDisks | Mode::SelectTableType | Mode::Confirm => { + // Labels + labels.push(vec![DVLine { + line_parts: vec![String::from("Source")], + line_colors: vec![Color::Cyan], + }]); + let dest_dv_line = DVLine { + line_parts: vec![ + String::from("Dest"), + String::from(" (WARNING: ALL DATA WILL BE DELETED!)"), + ], + line_colors: vec![Color::Cyan, Color::Red], + }; + if let Some(table_type) = &app.clone.table_type { + // Show table type + let type_str = match table_type { + PartitionTableType::Guid => "GPT", + PartitionTableType::Legacy => "MBR", + }; + labels.push(vec![ + dest_dv_line, + DVLine { + line_parts: vec![format!(" (Will be formatted {type_str})")], + line_colors: vec![Color::Yellow], + }, + ]); + } else { + labels.push(vec![dest_dv_line]); + } + let disk_list = app.clone.disk_list.lock().unwrap(); + disk_list + .iter() + .for_each(|disk| items.push(get_disk_description_right(&disk))); + } + Mode::SelectParts => { + vec!["Boot", "OS"].iter().for_each(|s| { + labels.push(vec![DVLine { + line_parts: vec![String::from(*s)], + line_colors: vec![Color::Cyan], + }]) + }); + if let Some(index) = app.clone.disk_index_dest { + start_index = 1; + let disk_list = app.clone.disk_list.lock().unwrap(); + if let Some(disk) = disk_list.get(index) { + // Disk Details + items.push(get_disk_description_right(&disk)); + + // Partition Details + disk.parts + .iter() + .for_each(|part| items.push(get_part_description(&part))); + } + } + } + _ => {} + } + Action::UpdateRight(labels, start_index, items) +} + +fn fortune() -> String { + String::from(match random::() / 4 { + 0 => "FUN FACT\n\n\nComputers barely work.", + 1 => "CRASH OVERRIDE\n\n\n\"Hack the planet!\"", + 2 => "CATS\n\n\n\"All your base are belong to us!\"", + 3 => "HMM\n\n\nThis has all happened before...\n\nThis will all happen again.", + 4 => "CYPHER\n\n\n\"I don’t even see the code. All I see is blonde, brunette, red-head.\"", + 5 => "CONGRATULATIONS\n\n\nYour did it!", + 6 => "DID YOU KNOW?\n\n\nmacOS includes a built-in screen reader!", + 7 => "TIP OF THE DAY\n\n\nNever go full Snappy!", + 8 => "WORDS OF WISDOM\n\n\n\nIt’s not DNS,\n\nThere’s no way it’s DNS,\n\nIt was DNS.", + 9 => "HAL 9000\n\n\n\"I'm sorry Dave, I'm afraid I can't do that.\"", + _ => "COMPLETE\n\n\nThank you for using this tool!", + }) +} diff --git a/deja_vu/src/components/left.rs b/deja_vu/src/components/left.rs deleted file mode 100644 index 8d61f72..0000000 --- a/deja_vu/src/components/left.rs +++ /dev/null @@ -1,464 +0,0 @@ -use std::fmt; - -// This file is part of Deja-vu. -// -// Deja-vu is free software: you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Deja-vu is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . -// -use color_eyre::Result; -use crossterm::event::KeyEvent; -use ratatui::{ - prelude::*, - widgets::{Block, Borders, HighlightSpacing, List, ListItem, Padding, Paragraph}, -}; -use tokio::sync::mpsc::UnboundedSender; -use tracing::info; - -use super::{popup, state::StatefulList, Component}; -use crate::{ - action::Action, - app::Mode, - config::Config, - system::{ - disk::{Disk, Partition, PartitionTableType}, - drivers::{self, Driver}, - }, -}; - -#[derive(Clone, Default)] -pub struct DiagMenu { - name: String, - action: Action, -} - -impl fmt::Display for DiagMenu { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", &self.name) - } -} - -#[derive(Default)] -pub struct Left { - command_tx: Option>, - config: Config, - diag_menu: StatefulList, - disk_id_dest: Option, - table_type: Option, - title_text: String, - list_disks: StatefulList, - list_drivers: StatefulList, - list_parts: StatefulList, - list_table_types: StatefulList, - mode: Mode, - selections: Vec>, -} - -impl Left { - pub fn new() -> Self { - let menu_entries = vec![ - DiagMenu { - name: String::from("Boot Diagnostics"), - action: Action::SetMode(Mode::BootDiags), - }, - DiagMenu { - name: String::from("Boot Setup"), - action: Action::SetMode(Mode::BootSetup), - }, - DiagMenu { - name: String::from("Inject Drivers"), - action: Action::SetMode(Mode::InjectDrivers), - }, - DiagMenu { - name: String::from("Toggle Safe Mode"), - action: Action::SetMode(Mode::ToggleSafeBoot), - }, - ]; - let mut diag_menu = StatefulList::default(); - diag_menu.set_items(menu_entries); - Self { - diag_menu, - selections: vec![None, None], - title_text: String::from("Home"), - ..Default::default() - } - } -} - -impl Component for Left { - fn handle_key_event(&mut self, key: KeyEvent) -> Result> { - let _ = key; // to appease clippy - Ok(None) - } - - fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { - self.command_tx = Some(tx); - Ok(()) - } - - fn register_config_handler(&mut self, config: Config) -> Result<()> { - self.config = config; - Ok(()) - } - - fn update(&mut self, action: Action) -> Result> { - match action { - Action::KeyUp => match self.mode { - Mode::DiagMenu => self.diag_menu.previous(), - Mode::InstallDrivers => self.list_drivers.previous(), - Mode::SelectDisks => self.list_disks.previous(), - Mode::SelectTableType => self.list_table_types.previous(), - Mode::SelectParts => self.list_parts.previous(), - _ => {} - }, - Action::KeyDown => match self.mode { - Mode::DiagMenu => self.diag_menu.next(), - Mode::InstallDrivers => self.list_drivers.next(), - Mode::SelectDisks => self.list_disks.next(), - Mode::SelectTableType => self.list_table_types.next(), - Mode::SelectParts => self.list_parts.next(), - _ => {} - }, - Action::Process => match self.mode { - // NOTE: Remove all variants except Mode::Confirm? - Mode::Confirm => { - if let Some(command_tx) = self.command_tx.clone() { - command_tx.send(Action::NextScreen)?; - } - } - Mode::DiagMenu - | Mode::InstallDrivers - | Mode::SelectDisks - | Mode::SelectTableType - | Mode::SelectParts => { - // Menu selection sections - let selection: Option = match self.mode { - Mode::DiagMenu => self.diag_menu.selected(), - Mode::InstallDrivers => self.list_drivers.selected(), - Mode::SelectDisks => self.list_disks.selected(), - Mode::SelectTableType => self.list_table_types.selected(), - Mode::SelectParts => self.list_parts.selected(), - _ => panic!("This shouldn't happen!"), - }; - if let Some(index) = selection { - if let Some(command_tx) = self.command_tx.clone() { - let mut selection_one: Option = None; - let mut selection_two: Option = None; - - // Get selection(s) - if self.selections[0].is_none() { - // First selection - selection_one = Some(index); - selection_two = None; - } else { - // Second selection - if let Some(source_index) = self.selections[0] { - if index == source_index { - // Toggle first selection - selection_one = None; - self.selections[0] = None; - } else { - selection_one = self.selections[0]; - selection_two = Some(index); - } - } - } - - // Send selection(s) if needed - // NOTE: This is needed to keep the app and all components in sync - match self.mode { - Mode::DiagMenu => { - // Only need to select one entry - if let Some(entry) = self.diag_menu.get_selected() { - command_tx.send(entry.action)?; - } - } - Mode::InstallDrivers => { - // Only need to select one entry - if let Some(driver) = self.list_drivers.get_selected() { - command_tx.send(Action::SelectDriver(driver.clone()))?; - } - } - Mode::SelectTableType => { - // Only need to select one entry - if let Some(table_type) = self.list_table_types.get_selected() { - self.table_type = Some(table_type.clone()); - command_tx.send(Action::SelectTableType(table_type))?; - } - } - Mode::SelectDisks | Mode::SelectParts => { - command_tx - .send(Action::Select(selection_one, selection_two))?; - - // Advance screen if both selections made - if selection_two.is_some() { - command_tx.send(Action::NextScreen)?; - } - } - _ => {} - }; - } - } - } - Mode::BootDiags | Mode::BootSetup | Mode::InjectDrivers | Mode::ToggleSafeBoot => { - if let Some(command_tx) = self.command_tx.clone() { - command_tx.send(Action::NextScreen)?; - } - } - _ => {} - }, - Action::Select(Some(index), None) => self.selections[0] = Some(index), - Action::Select(_, Some(index)) => { - if self.mode == Mode::SelectDisks { - self.disk_id_dest = Some(index); - } - } - Action::SetMode(new_mode) => { - let prev_mode = self.mode; - self.mode = new_mode; - match (prev_mode, new_mode) { - (_, Mode::ScanDisks) => { - self.list_disks.clear_items(); - self.title_text = String::new(); - } - (_, Mode::InstallDrivers) => { - self.list_drivers.set_items(drivers::scan()); - self.selections[0] = None; - self.selections[1] = None; - self.title_text = String::from("Install Drivers"); - if self.list_drivers.is_empty() { - if let Some(command_tx) = self.command_tx.clone() { - command_tx.send(Action::DisplayPopup( - popup::Type::Error, - String::from("No drivers available to install"), - ))?; - } - } - } - (_, Mode::SelectDisks) => { - self.selections[0] = None; - self.selections[1] = None; - self.title_text = String::from("Select Source and Destination Disks"); - } - (_, Mode::SelectTableType) => { - self.list_table_types - .set_items(vec![PartitionTableType::Guid, PartitionTableType::Legacy]); - self.selections[0] = None; - self.selections[1] = None; - self.title_text = String::from("Select Partition Table Type"); - } - (_, Mode::PreClone | Mode::Clone | Mode::PostClone) => { - self.title_text = String::from("Processing"); - } - (_, Mode::SelectParts) => { - self.title_text = String::from("Select Boot and OS Partitions"); - } - (Mode::SelectDisks | Mode::SelectParts, Mode::Confirm) => { - self.title_text = String::from("Confirm Selections"); - } - (Mode::SelectTableType, Mode::Confirm) => { - self.title_text = String::from("Confirm Selections (Again)"); - } - (_, Mode::Done | Mode::Failed) => self.title_text = String::from("Done"), - // Diagnostic states - (_, Mode::DiagMenu) => self.title_text = String::from("Main Menu"), - (_, Mode::BootDiags) => self.title_text = String::from("Boot Diagnostics"), - (_, Mode::BootSetup) => self.title_text = String::from("Boot Setup"), - (_, Mode::InjectDrivers) => self.title_text = String::from("Inject Drivers"), - (_, Mode::ToggleSafeBoot) => self.title_text = String::from("Toggle Safe Mode"), - - // Invalid states - (_, Mode::Confirm) => panic!("This shouldn't happen."), - } - } - Action::UpdateDiskList(disks) => { - info!("Updating disk list"); - self.list_disks.set_items(disks); - if self.mode == Mode::Clone { - if let Some(index) = self.disk_id_dest { - if let Some(disk) = self.list_disks.get(index) { - self.list_parts.set_items(disk.get_parts()); - - // Auto-select first partition and highlight likely OS partition - if let Some(table_type) = &self.table_type { - match table_type { - PartitionTableType::Guid => { - if disk.num_parts() >= 3 { - self.selections[0] = Some(0); - self.list_parts.select(2); - } - } - PartitionTableType::Legacy => { - if disk.num_parts() >= 2 { - self.selections[0] = Some(0); - self.list_parts.select(1); - } - } - } - } - } - } - } - } - _ => {} - } - Ok(None) - } - - fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { - let [title_area, body_area] = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Length(1), Constraint::Min(1)]) - .areas(area); - - // Title - let title = - Paragraph::new(Line::from(Span::styled(&self.title_text, Style::default())).centered()) - .block(Block::default().borders(Borders::NONE)); - frame.render_widget(title, title_area); - - // Body - match self.mode { - // Clone modes - Mode::ScanDisks - | Mode::PreClone - | Mode::Clone - | Mode::PostClone - // Diagnostic modes - | Mode::BootDiags - | Mode::BootSetup - | Mode::InjectDrivers - | Mode::ToggleSafeBoot - // Done - | Mode::Done - | Mode::Failed => { - // Leave blank - let paragraph = Paragraph::new(String::new()).block( - Block::default() - .borders(Borders::ALL) - .padding(Padding::new(1, 1, 1, 1)), - ); - frame.render_widget(paragraph, body_area); - - // Bail early - return Ok(()); - } - Mode::Confirm => { - // Nag the user - let paragraph = Paragraph::new(String::from("Are the listed selections correct?")) - .block( - Block::default() - .borders(Borders::ALL) - .padding(Padding::new(1, 1, 1, 1)), - ); - frame.render_widget(paragraph, body_area); - - // Bail early - return Ok(()); - } - Mode::DiagMenu - | Mode::InstallDrivers - | Mode::SelectDisks - | Mode::SelectTableType - | Mode::SelectParts => { - // List modes - let mut list_items = Vec::::new(); - let list_items_strings: Vec = match self.mode { - Mode::DiagMenu=> self - .diag_menu - .items - .iter() - .map(|i| format!("{i}")) - .collect(), - Mode::InstallDrivers => self - .list_drivers - .items - .iter() - .map(|i| format!("{i}")) - .collect(), - Mode::SelectDisks => self - .list_disks - .items - .iter() - .map(|i| format!("{i}")) - .collect(), - Mode::SelectTableType => self - .list_table_types - .items - .iter() - .map(|i| format!("{i}")) - .collect(), - Mode::SelectParts => self - .list_parts - .items - .iter() - .map(|i| format!("{i}")) - .collect(), - _ => panic!("This shouldn't happen."), - }; - if !list_items_strings.is_empty() { - for (index, item) in list_items_strings.iter().enumerate() { - let mut item_style = Style::default(); - let mut item_text_tail = ""; - if let Some(selection_one) = self.selections[0] { - if selection_one == index { - item_style = Style::new().yellow(); - item_text_tail = match self.mode { - Mode::SelectDisks => " ~source disk~", - Mode::SelectParts => " ~boot volume~", - _ => "", - } - } - } - list_items.push( - ListItem::new(format!(" {item}\n{item_text_tail}\n\n")) - .style(item_style), - ); - } - } - let list = List::new(list_items) - .block( - Block::default() - .borders(Borders::ALL) - .padding(Padding::new(1, 1, 1, 1)), - ) - .highlight_spacing(HighlightSpacing::Always) - .highlight_style(Style::new().green().bold()) - .highlight_symbol(" --> ") - .repeat_highlight_symbol(false); - match self.mode { - Mode::DiagMenu=> { - frame.render_stateful_widget(list, body_area, &mut self.diag_menu.state); - } - Mode::InstallDrivers => { - frame.render_stateful_widget(list, body_area, &mut self.list_drivers.state); - } - Mode::SelectDisks => { - frame.render_stateful_widget(list, body_area, &mut self.list_disks.state); - } - Mode::SelectTableType => frame.render_stateful_widget( - list, - body_area, - &mut self.list_table_types.state, - ), - Mode::SelectParts => { - frame.render_stateful_widget(list, body_area, &mut self.list_parts.state); - } - _ => panic!("This shouldn't happen."), - } - } - } - - // Done - Ok(()) - } -} diff --git a/deja_vu/src/components/right.rs b/deja_vu/src/components/right.rs deleted file mode 100644 index 61814d3..0000000 --- a/deja_vu/src/components/right.rs +++ /dev/null @@ -1,401 +0,0 @@ -// This file is part of Deja-vu. -// -// Deja-vu is free software: you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Deja-vu is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . -// -use color_eyre::Result; -use crossterm::event::KeyEvent; -use ratatui::{ - prelude::*, - widgets::{Block, Borders, Padding, Paragraph, Wrap}, -}; -use tokio::sync::mpsc::UnboundedSender; -use tracing::info; - -use super::{state::StatefulList, Component}; -use crate::{ - action::Action, - app::Mode, - config::Config, - system::{ - cpu::get_cpu_name, - disk::{Disk, Partition, PartitionTableType}, - }, -}; - -#[derive(Default)] -pub struct Right { - command_tx: Option>, - config: Config, - cur_mode: Mode, - list_disks: StatefulList, - list_parts: StatefulList, - prev_mode: Mode, - selected_disks: Vec>, - selections: Vec>, - table_type: Option, -} - -impl Right { - pub fn new() -> Self { - Self { - selected_disks: vec![None, None], - selections: vec![None, None], - ..Default::default() - } - } -} - -impl Component for Right { - fn handle_key_event(&mut self, key: KeyEvent) -> Result> { - let _ = key; // to appease clippy - Ok(None) - } - - fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { - self.command_tx = Some(tx); - Ok(()) - } - - fn register_config_handler(&mut self, config: Config) -> Result<()> { - self.config = config; - Ok(()) - } - - fn update(&mut self, action: Action) -> Result> { - match action { - Action::KeyUp => match self.cur_mode { - Mode::SelectDisks => self.list_disks.previous(), - Mode::SelectParts => self.list_parts.previous(), - _ => {} - }, - Action::KeyDown => match self.cur_mode { - Mode::SelectDisks => self.list_disks.next(), - Mode::SelectParts => self.list_parts.next(), - _ => {} - }, - Action::Process => { - if self.prev_mode == Mode::SelectDisks && self.cur_mode == Mode::Confirm { - self.selected_disks.clone_from(&self.selections); - } - } - Action::Select(one, two) => { - self.selections[0] = one; - self.selections[1] = two; - } - Action::SelectTableType(table_type) => self.table_type = Some(table_type), - Action::SetMode(new_mode) => { - self.prev_mode = self.cur_mode; - self.cur_mode = new_mode; - match self.cur_mode { - Mode::SelectDisks => { - self.selections[0] = None; - self.selections[1] = None; - self.selected_disks[0] = None; - self.selected_disks[1] = None; - } - Mode::SelectParts => { - self.selections[0] = None; - self.selections[1] = None; - } - Mode::SelectTableType => { - self.selections[0] = None; - self.selections[1] = None; - self.table_type = None; - } - _ => {} - } - } - Action::UpdateDiskList(disks) => { - info!("Updating disk list"); - self.list_disks.set_items(disks); - if self.cur_mode == Mode::Clone { - if let Some(index) = self.selected_disks[1] { - if let Some(disk) = self.list_disks.get(index) { - self.list_parts.set_items(disk.get_parts()); - - // Auto-select first partition and highlight likely OS partition - if let Some(index) = self.selected_disks[1] { - if let Some(disk) = self.list_disks.get(index) { - if let Some(table_type) = &self.table_type { - match table_type { - PartitionTableType::Guid => { - if disk.num_parts() >= 3 { - self.selections[0] = Some(0); - self.list_parts.select(2); - } - } - PartitionTableType::Legacy => { - if disk.num_parts() >= 2 { - self.selections[0] = Some(0); - self.list_parts.select(1); - } - } - } - } - } - } - } - } - } - } - _ => {} - } - Ok(None) - } - - fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { - let [title_area, body_area] = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Length(1), Constraint::Min(1)]) - .areas(area); - - // Title - let title_text = String::from("Info"); - let title = Paragraph::new(Line::from(title_text).centered()) - .block(Block::default().borders(Borders::NONE)); - frame.render_widget(title, title_area); - - // Body - let mut body_text = Vec::new(); - match (self.prev_mode, self.cur_mode) { - (_, Mode::InstallDrivers) => { - body_text.push(Line::from(Span::raw(format!("CPU: {}", get_cpu_name())))); - } - (_, Mode::SelectDisks | Mode::SelectTableType) - | (Mode::SelectDisks | Mode::SelectTableType, Mode::Confirm) => { - // Source Disk - body_text.push(Line::from(Span::styled( - "Source:", - Style::default().cyan().bold(), - ))); - body_text.push(Line::from("")); - body_text.push(Line::from(Span::styled( - format!( - "{:<8} {:>11} {:<4} {:<4} {}", - "Disk ID", "Size", "Conn", "Type", "Model (Serial)" - ), - Style::new().green().bold(), - ))); - let index = if self.selected_disks[0].is_some() { - // Selected in prior mode - self.selected_disks[0] - } else if self.selections[0].is_some() { - // Selected in this mode - self.selections[0] - } else { - // Highlighted entry - self.list_disks.selected() - }; - if let Some(i) = index { - if let Some(disk) = self.list_disks.get(i) { - body_text.push(Line::from(Span::raw(&disk.description))); - - // Source parts - body_text.push(Line::from("")); - body_text.push(Line::from(Span::styled( - format!( - "{:<8} {:>11} {:<7} {}", - "Part ID", "Size", "(FS)", "\"Label\"" - ), - Style::new().blue().bold(), - ))); - for line in &disk.parts_description { - body_text.push(Line::from(Span::raw(line))); - } - } - } - - // Destination Disk - let index = if self.selected_disks[1].is_some() { - // Selected in prior mode - self.selected_disks[1] - } else { - // Select(ed) in this mode - match (self.selections[0], self.selections[1]) { - (Some(one), None) => { - // First selected - if let Some(two) = self.selections[1] { - if one == two { - None - } else { - self.selections[1] - } - } else { - self.list_disks.selected() - } - } - (Some(_), Some(_)) => { - // Both selected - self.selections[1] - } - (_, _) => None, - } - }; - if let Some(i) = index { - // Divider - body_text.push(Line::from("")); - body_text.push(Line::from(str::repeat("─", (body_area.width - 4) as usize))); - body_text.push(Line::from("")); - - // Disk - if let Some(disk) = self.list_disks.get(i) { - body_text.push(Line::from(vec![ - Span::styled("Dest:", Style::default().cyan().bold()), - Span::styled( - " (WARNING: ALL DATA WILL BE DELETED!)", - Style::default().red().bold(), - ), - ])); - if let Some(table_type) = &self.table_type { - body_text.push(Line::from(Span::styled( - format!(" (Will be formatted {table_type})"), - Style::default().yellow().bold(), - ))); - } - body_text.push(Line::from("")); - body_text.push(Line::from(Span::styled( - format!( - "{:<8} {:>11} {:<4} {:<4} {}", - "Disk ID", "Size", "Conn", "Type", "Model (Serial)" - ), - Style::new().green().bold(), - ))); - body_text.push(Line::from(Span::raw(&disk.description))); - - // Destination parts - body_text.push(Line::from("")); - body_text.push(Line::from(Span::styled( - format!( - "{:<8} {:>11} {:<7} {}", - "Part ID", "Size", "(FS)", "\"Label\"" - ), - Style::new().blue().bold(), - ))); - for line in &disk.parts_description { - body_text.push(Line::from(Span::raw(line))); - } - } - } - } - (_, Mode::SelectParts) | (Mode::SelectParts, Mode::Confirm) => { - // Disk - if let Some(index) = self.selected_disks[1] { - if let Some(disk) = self.list_disks.get(index) { - body_text.push(Line::from(Span::styled( - "Dest:", - Style::default().cyan().bold(), - ))); - body_text.push(Line::from("")); - body_text.push(Line::from(Span::styled( - format!( - "{:<8} {:>11} {:<4} {:<4} {}", - "Disk ID", "Size", "Conn", "Type", "Model (Serial)" - ), - Style::new().green().bold(), - ))); - body_text.push(Line::from(Span::raw(&disk.description))); - - // Destination parts - body_text.push(Line::from("")); - body_text.push(Line::from(Span::styled( - format!( - "{:<8} {:>11} {:<7} {}", - "Part ID", "Size", "(FS)", "\"Label\"" - ), - Style::new().blue().bold(), - ))); - for line in &disk.parts_description { - body_text.push(Line::from(Span::raw(line))); - } - } - - // Divider - body_text.push(Line::from("")); - body_text.push(Line::from(str::repeat("─", (body_area.width - 4) as usize))); - body_text.push(Line::from("")); - - // Boot Partition - // i.e. either the previously selected part or the highlighted one (if possible) - let mut boot_index = self.selections[0]; - if boot_index.is_none() { - if let Some(i) = self.list_parts.selected() { - boot_index = Some(i); - } - } - if let Some(i) = boot_index { - if let Some(part) = self.list_parts.get(i) { - body_text.push(Line::from(Span::styled( - "Boot:", - Style::default().cyan().bold(), - ))); - body_text.push(Line::from("")); - body_text.push(Line::from(Span::styled( - format!( - "{:<8} {:>11} {:<7} {}", - "Part ID", "Size", "(FS)", "\"Label\"" - ), - Style::new().blue().bold(), - ))); - body_text.push(Line::from(Span::raw(format!("{part}")))); - } - } - - // OS Partition - // i.e. either the previously selected part or the highlighted one (if needed) - let mut os_index = self.selections[1]; - if os_index.is_none() { - if let Some(boot_index) = self.selections[0] { - if let Some(i) = self.list_parts.selected() { - if boot_index != i { - os_index = Some(i); - } - } - } - } - if let Some(i) = os_index { - if let Some(part) = self.list_parts.get(i) { - body_text.push(Line::from("")); - body_text.push(Line::from(Span::styled( - "OS:", - Style::default().cyan().bold(), - ))); - body_text.push(Line::from("")); - body_text.push(Line::from(Span::styled( - format!( - "{:<8} {:>11} {:<7} {}", - "Part ID", "Size", "(FS)", "\"Label\"" - ), - Style::new().blue().bold(), - ))); - body_text.push(Line::from(Span::raw(format!("{part}")))); - } - } - } - } - _ => {} - } - let body = Paragraph::new(body_text) - .style(Style::default().fg(Color::Gray)) - .wrap(Wrap { trim: false }) - .block( - Block::default() - .borders(Borders::ALL) - .padding(Padding::new(1, 1, 1, 1)), - ); - frame.render_widget(body, body_area); - - // Done - Ok(()) - } -} diff --git a/deja_vu/src/main.rs b/deja_vu/src/main.rs index 326069a..8bcfda9 100644 --- a/deja_vu/src/main.rs +++ b/deja_vu/src/main.rs @@ -14,30 +14,20 @@ // along with Deja-vu. If not, see . // use clap::Parser; -use cli::Cli; use color_eyre::Result; +use core; use crate::app::App; -mod action; mod app; -mod cli; -mod components; -mod config; -mod errors; -mod logging; -mod system; -mod tasks; -mod tests; -mod tui; #[tokio::main] async fn main() -> Result<()> { - crate::errors::init()?; - crate::logging::init()?; + core::errors::init()?; + core::logging::init()?; - let args = Cli::parse(); - let mut app = App::new(args.cli_cmd, args.tick_rate, args.frame_rate)?; + let args = core::cli::Cli::parse(); + let mut app = App::new(args.tick_rate, args.frame_rate)?; app.run().await?; Ok(()) } diff --git a/include/menu_entries/01_deja-vu.toml b/include/menu_entries/01_deja-vu.toml index 6899622..2f33c07 100644 --- a/include/menu_entries/01_deja-vu.toml +++ b/include/menu_entries/01_deja-vu.toml @@ -1,4 +1,5 @@ name = 'Deja-Vu' command = 'X:\tools\deja-vu.exe' +description = "Windows clone assistant tool" use_conemu = true separator = false diff --git a/include/menu_entries/02_separator.toml b/include/menu_entries/02_separator.toml index ed50f1c..cfd668c 100644 --- a/include/menu_entries/02_separator.toml +++ b/include/menu_entries/02_separator.toml @@ -1,4 +1,5 @@ name = '' command = '' +description = '' use_conemu = false separator = true diff --git a/include/menu_entries/03_ntpwedit.toml b/include/menu_entries/03_ntpwedit.toml index 9b07df9..d8124dd 100644 --- a/include/menu_entries/03_ntpwedit.toml +++ b/include/menu_entries/03_ntpwedit.toml @@ -1,4 +1,5 @@ name = 'NTPWEdit' command = 'X:\Program Files\NTPWEdit\ntpwedit.exe' +description = 'Mostly used to unlock the built-in admin account' use_conemu = false separator = false diff --git a/include/menu_entries/04_clone-tool.toml b/include/menu_entries/04_clone-tool.toml index 1fe2dd3..530ddd3 100644 --- a/include/menu_entries/04_clone-tool.toml +++ b/include/menu_entries/04_clone-tool.toml @@ -1,4 +1,5 @@ name = 'Some Clone Tool' command = 'X:\Program Files\Some\Tool.exe' +description = 'Run Some Clone tool' use_conemu = false separator = false diff --git a/include/menu_entries/05_taskmgr.toml b/include/menu_entries/05_taskmgr.toml index c33d623..f4484fc 100644 --- a/include/menu_entries/05_taskmgr.toml +++ b/include/menu_entries/05_taskmgr.toml @@ -1,4 +1,5 @@ name = 'Task Manager' command = 'X:\Windows\System32\taskmgr.exe' +description = 'Manage those tasks' use_conemu = false separator = false diff --git a/include/pe-menu.toml b/include/pe-menu.toml deleted file mode 100644 index 45f8b6b..0000000 --- a/include/pe-menu.toml +++ /dev/null @@ -1,17 +0,0 @@ -# This file is part of Deja-vu. -# -# Deja-vu is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Deja-vu is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Deja-vu. If not, see . - -con_emu = 'X:\Program Files\ConEmu\ConEmu64.exe' -tools = [] diff --git a/pe_menu/Cargo.toml b/pe_menu/Cargo.toml index 4f78c30..cf7bcfb 100644 --- a/pe_menu/Cargo.toml +++ b/pe_menu/Cargo.toml @@ -16,15 +16,31 @@ [package] name = "pe-menu" authors = ["2Shirt <2xShirt@gmail.com>"] -description = "Menu for WinPE." edition = "2021" license = "GPL" -version = "0.1.0" +version = "0.2.0" [dependencies] -crossterm = { version = "0.27.0", features = ["event-stream"] } +core = { path = "../core" } +clap = { version = "4.4.5", features = [ + "derive", + "cargo", + "wrap_help", + "unicode", + "string", + "unstable-styles", + ] } +color-eyre = "0.6.3" +crossterm = { version = "0.28.1", features = ["event-stream"] } futures = "0.3.30" -ratatui = "0.26.0" -serde = { version = "1.0.202", features = ["derive"] } -tokio = { version = "1.35.1", features = ["full"] } +ratatui = "0.29.0" +serde = { version = "1.0.217", features = ["derive"] } +tokio = { version = "1.43.0", features = ["full"] } toml = "0.8.13" +tracing = "0.1.41" +tracing-error = "0.2.0" +tracing-subscriber = { version = "0.3.18", features = ["env-filter", "serde"] } + +[build-dependencies] +anyhow = "1.0.86" +vergen-gix = { version = "1.0.0", features = ["build", "cargo"] } diff --git a/pe_menu/build.rs b/pe_menu/build.rs new file mode 100644 index 0000000..c3ced6b --- /dev/null +++ b/pe_menu/build.rs @@ -0,0 +1,28 @@ +// This file is part of Deja-vu. +// +// Deja-vu is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Deja-vu is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Deja-vu. If not, see . +// +use anyhow::Result; +use vergen_gix::{BuildBuilder, CargoBuilder, Emitter, GixBuilder}; + +fn main() -> Result<()> { + let build = BuildBuilder::all_build()?; + let gix = GixBuilder::all_git()?; + let cargo = CargoBuilder::all_cargo()?; + Emitter::default() + .add_instructions(&build)? + .add_instructions(&gix)? + .add_instructions(&cargo)? + .emit() +} diff --git a/pe_menu/src/app.rs b/pe_menu/src/app.rs index fbe5c23..5f9c060 100644 --- a/pe_menu/src/app.rs +++ b/pe_menu/src/app.rs @@ -13,354 +13,418 @@ // You should have received a copy of the GNU General Public License // along with Deja-vu. If not, see . // -use ratatui::widgets::ListState; -use serde::Deserialize; +use core::{ + action::Action, + components::{ + footer::Footer, fps::FpsCounter, left::Left, popup, right::Right, state::StatefulList, + title::Title, Component, + }, + config::Config, + line::DVLine, + state::Mode, + tasks::{Task, Tasks}, + tui::{Event, Tui}, +}; use std::{ - env, error, fs, io, + env, fs, + iter::zip, path::PathBuf, - process::{Command, Output}, - thread::{self, JoinHandle}, + sync::{Arc, Mutex}, }; -/// Application result type. -#[allow(clippy::module_name_repetitions)] -pub type AppResult = std::result::Result>; +use color_eyre::Result; +use ratatui::{ + crossterm::event::KeyEvent, + layout::{Constraint, Direction, Layout}, + prelude::Rect, + style::Color, +}; +use serde::Deserialize; +use tokio::sync::mpsc; +use tracing::{debug, info}; -/// Application exit reasons -#[derive(Debug, Default)] -pub enum QuitReason { - #[default] - Exit, - Poweroff, - Restart, -} - -/// Config -#[derive(Debug, Deserialize)] -pub struct Config { - con_emu: String, - tools: Vec, -} - -impl Config { - /// # Panics - /// - /// Will panic for many reasons - #[must_use] - pub fn load() -> Option { - // Main config - let exe_path = env::current_exe().expect("Failed to find main executable"); - let contents = fs::read_to_string(exe_path.with_file_name("pe-menu.toml")) - .expect("Failed to load config file"); - let mut new_config: Config = - toml::from_str(&contents).expect("Failed to parse config file"); - - // Tools - let tool_config_path = exe_path.parent().unwrap().join("menu_entries"); - let mut entries: Vec = std::fs::read_dir(tool_config_path) - .expect("Failed to find any tool configs") - .map(|res| res.map(|e| e.path())) - .filter_map(Result::ok) - .collect(); - entries.sort(); - for entry in entries { - let contents = fs::read_to_string(&entry).expect("Failed to read tool config file"); - let tool: Tool = toml::from_str(&contents).expect("Failed to parse tool config file"); - new_config.tools.push(tool); - } - - // Done - Some(new_config) - } -} - -/// `PopUp` -#[derive(Debug, Clone, PartialEq)] -pub struct PopUp { - pub title: String, - pub body: String, -} - -impl PopUp { - #[must_use] - pub fn new(title: &str, body: &str) -> PopUp { - PopUp { - title: String::from(title), - body: String::from(body), - } - } -} - -/// `Tool` -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Default, Deserialize)] pub struct Tool { name: String, command: String, + description: String, args: Option>, use_conemu: bool, separator: bool, } -/// `MenuEntry` -#[derive(Default, Debug, Clone, PartialEq)] -pub struct MenuEntry { - pub name: String, - pub command: String, - pub args: Vec, - pub use_conemu: bool, - pub separator: bool, -} - -impl MenuEntry { - #[must_use] - pub fn new( - name: &str, - command: &str, - args: Option>, - use_conemu: bool, - separator: bool, - ) -> MenuEntry { - let mut my_args = Vec::new(); - if let Some(a) = args { - my_args.clone_from(&a); - } - MenuEntry { - name: String::from(name), - command: String::from(command), - args: my_args, - use_conemu, - separator, - } - } -} - -/// `StatefulList` -#[derive(Default, Debug, Clone, PartialEq)] -pub struct StatefulList { - pub state: ListState, - pub items: Vec, - pub last_selected: Option, -} - -impl StatefulList { - #[must_use] - pub fn new() -> StatefulList { - StatefulList { - state: ListState::default(), - items: Vec::new(), - last_selected: None, - } - } - - #[must_use] - pub fn get_selected(&self) -> Option<&T> { - if let Some(i) = self.state.selected() { - self.items.get(i) - } else { - None - } - } - - pub fn pop_selected(&mut self) -> Option { - if let Some(i) = self.state.selected() { - Some(self.items[i].clone()) - } else { - None - } - } - - fn select_first_item(&mut self) { - if self.items.is_empty() { - self.state.select(None); - } else { - self.state.select(Some(0)); - } - self.last_selected = None; - } - - pub fn set_items(&mut self, items: Vec) { - // Clear list and rebuild with provided items - self.items.clear(); - for item in items { - self.items.push(item); - } - - // Reset state and select first item (if available) - self.state = ListState::default(); - self.select_first_item(); - } - - pub fn next(&mut self) { - if self.items.is_empty() { - return; - } - let i = match self.state.selected() { - Some(i) => { - if i >= self.items.len() - 1 { - 0 - } else { - i + 1 - } - } - None => self.last_selected.unwrap_or(0), - }; - self.state.select(Some(i)); - } - - pub fn previous(&mut self) { - if self.items.is_empty() { - return; - } - let i = match self.state.selected() { - Some(i) => { - if i == 0 { - self.items.len() - 1 - } else { - i - 1 - } - } - None => self.last_selected.unwrap_or(0), - }; - self.state.select(Some(i)); - } -} - -/// Application. -#[derive(Debug)] pub struct App { - pub config: Config, - pub main_menu: StatefulList, - pub popup: Option, - pub quit_reason: QuitReason, - pub running: bool, - pub thread_pool: Vec>>, -} - -impl Default for App { - fn default() -> Self { - let config = Config::load(); - Self { - config: config.unwrap(), - running: true, - quit_reason: QuitReason::Exit, - main_menu: StatefulList::new(), - popup: None, - thread_pool: Vec::new(), - } - } + // TUI + action_rx: mpsc::UnboundedReceiver, + action_tx: mpsc::UnboundedSender, + components: Vec>, + config: Config, + frame_rate: f64, + last_tick_key_events: Vec, + should_quit: bool, + should_suspend: bool, + tick_rate: f64, + // App + list: StatefulList, + mode: Mode, + tasks: Tasks, } impl App { - /// Constructs a new instance of [`App`]. - #[must_use] - pub fn new() -> Self { - let mut app = Self::default(); - - // Add MenuEntries - for tool in &app.config.tools { - app.main_menu.items.push(MenuEntry::new( - &tool.name, - &tool.command, - tool.args.clone(), - tool.use_conemu, - tool.separator, - )); - } - app.main_menu.select_first_item(); - - // Done - app + pub fn new(tick_rate: f64, frame_rate: f64) -> Result { + let (action_tx, action_rx) = mpsc::unbounded_channel(); + let disk_list_arc = Arc::new(Mutex::new(Vec::new())); + let tasks = Tasks::new(action_tx.clone(), disk_list_arc.clone()); + let mut list = StatefulList::default(); + list.set_items(load_tools()); + Ok(Self { + // TUI + action_rx, + action_tx, + components: vec![ + Box::new(Title::new("PE Menu")), + Box::new(FpsCounter::new()), + Box::new(Left::new()), + Box::new(Right::new()), + Box::new(Footer::new()), + Box::new(popup::Popup::new()), + ], + config: Config::new()?, + frame_rate, + last_tick_key_events: Vec::new(), + should_quit: false, + should_suspend: false, + tick_rate, + // App + list, + mode: Mode::default(), + tasks, + }) } - /// Handles the tick event of the terminal. - pub fn tick(&self) {} + pub async fn run(&mut self) -> Result<()> { + let mut tui = Tui::new()? + // .mouse(true) // uncomment this line to enable mouse support + .tick_rate(self.tick_rate) + .frame_rate(self.frame_rate); + tui.enter()?; - /// Actually exit application - /// - /// # Errors - /// # Panics - /// - /// Will panic if wpeutil fails to reboot or shutdown - pub fn exit(&self) -> Result<(), &'static str> { - let mut argument: Option = None; - match self.quit_reason { - QuitReason::Exit => {} - QuitReason::Poweroff => argument = Some(String::from("shutdown")), - QuitReason::Restart => argument = Some(String::from("reboot")), + for component in &mut self.components { + component.register_action_handler(self.action_tx.clone())?; } - if let Some(a) = argument { - Command::new("wpeutil") - .arg(a) - .output() - .expect("Failed to run exit command"); + for component in &mut self.components { + component.register_config_handler(self.config.clone())?; + } + for component in &mut self.components { + component.init(tui.size()?)?; + } + + let action_tx = self.action_tx.clone(); + action_tx.send(Action::SetMode(Mode::PEMenu))?; + loop { + self.handle_events(&mut tui).await?; + self.handle_actions(&mut tui)?; + if self.should_suspend { + tui.suspend()?; + action_tx.send(Action::Resume)?; + action_tx.send(Action::ClearScreen)?; + // tui.mouse(true); + tui.enter()?; + } else if self.should_quit { + tui.stop()?; + break; + } + } + tui.exit()?; + Ok(()) + } + + async fn handle_events(&mut self, tui: &mut Tui) -> Result<()> { + let Some(event) = tui.next_event().await else { + return Ok(()); + }; + let action_tx = self.action_tx.clone(); + match event { + Event::Quit => action_tx.send(Action::Quit)?, + Event::Tick => action_tx.send(Action::Tick)?, + Event::Render => action_tx.send(Action::Render)?, + Event::Resize(x, y) => action_tx.send(Action::Resize(x, y))?, + Event::Key(key) => self.handle_key_event(key)?, + _ => {} + } + for component in &mut self.components { + if let Some(action) = component.handle_events(Some(event.clone()))? { + action_tx.send(action)?; + } } Ok(()) } - /// Set running to false to quit the application. - pub fn quit(&mut self, reason: QuitReason) { - self.running = false; - self.quit_reason = reason; - } - - /// # Panics - /// - /// Will panic if command fails to run - pub fn open_terminal(&mut self) { - Command::new("cmd.exe") - .arg("-new_console:n") - .output() - .expect("Failed to run command"); - } - - /// # Panics - /// - /// Will panic if menu entry isn't found - pub fn run_tool(&mut self) { - // Command - let tool: &MenuEntry; - if let Some(index) = self.main_menu.state.selected() { - tool = &self.main_menu.items[index]; - } else { - self.popup = Some(PopUp::new( - "Failed to find menu entry", - "Check for an updated version of Deja-Vu", - )); - return; - } - let command = if tool.use_conemu { - self.config.con_emu.clone() - } else { - tool.command.clone() + fn handle_key_event(&mut self, key: KeyEvent) -> Result<()> { + let action_tx = self.action_tx.clone(); + let Some(keymap) = self.config.keybindings.get(&self.mode) else { + return Ok(()); }; - - // Separators - if tool.separator { - return; - } - - // Args - let mut args = tool.args.clone(); - if tool.use_conemu { - args.insert(0, tool.command.clone()); - args.push(String::from("-new_console:n")); - } - - // Check path - let command_path = PathBuf::from(&command); - if let Ok(true) = command_path.try_exists() { - // File path exists + if let Some(action) = keymap.get(&vec![key]) { + info!("Got action: {action:?}"); + action_tx.send(action.clone())?; } else { - // File path doesn't exist or is a broken symlink/etc - // The latter case would be Ok(false) rather than Err(_) - self.popup = Some(PopUp::new("Tool Missing", &format!("Tool path: {command}"))); - return; - } + // If the key was not handled as a single key action, + // then consider it for multi-key combinations. + self.last_tick_key_events.push(key); - // Run - // TODO: This really needs refactored to use channels so we can properly check if the - // command fails. - let new_thread = thread::spawn(move || Command::new(command_path).args(args).output()); - self.thread_pool.push(new_thread); + // Check for multi-key combinations + if let Some(action) = keymap.get(&self.last_tick_key_events) { + info!("Got action: {action:?}"); + action_tx.send(action.clone())?; + } + } + Ok(()) + } + + fn handle_actions(&mut self, tui: &mut Tui) -> Result<()> { + while let Ok(action) = self.action_rx.try_recv() { + if action != Action::Tick && action != Action::Render { + debug!("{action:?}"); + } + match action { + Action::Tick => { + self.last_tick_key_events.drain(..); + // Check background task(s) + self.tasks.poll()?; + } + Action::Quit => self.should_quit = true, + Action::Suspend => self.should_suspend = true, + Action::Resume => self.should_suspend = false, + Action::ClearScreen => tui.terminal.clear()?, + Action::KeyUp => { + self.list.previous(); + if let Some(tool) = self.list.get_selected() { + if tool.separator { + // Skip over separator + self.list.previous(); + if let Some(index) = self.list.selected() { + self.action_tx.send(Action::Highlight(index))?; + } + } + } + } + Action::KeyDown => { + self.list.next(); + if let Some(tool) = self.list.get_selected() { + if tool.separator { + // Skip over separator + self.list.next(); + if let Some(index) = self.list.selected() { + self.action_tx.send(Action::Highlight(index))?; + } + } + } + } + Action::Error(ref msg) => { + self.action_tx + .send(Action::DisplayPopup(popup::Type::Error, msg.clone()))?; + self.action_tx.send(Action::SetMode(Mode::Failed))?; + } + Action::Process => { + // Run selected tool + if let Some(tool) = self.list.get_selected() { + info!("Run tool: {:?}", &tool); + self.tasks.add(build_command(&self, &tool)); + } + } + Action::Resize(w, h) => self.handle_resize(tui, w, h)?, + Action::Render => self.render(tui)?, + Action::SetMode(mode) => { + self.mode = mode; + self.action_tx + .send(Action::UpdateFooter(String::from("(Enter) to select")))?; + self.action_tx.send(build_left_items(self))?; + self.action_tx.send(build_right_items(self))?; + self.action_tx.send(Action::Select(None, None))?; + } + Action::OpenTerminal => { + self.tasks.add(Task::Command( + PathBuf::from("cmd.exe"), + vec![String::from("-new_console:n")], + )); + } + _ => {} + } + for component in &mut self.components { + if let Some(action) = component.update(action.clone())? { + self.action_tx.send(action)?; + }; + } + } + Ok(()) + } + + fn handle_resize(&mut self, tui: &mut Tui, w: u16, h: u16) -> Result<()> { + tui.resize(Rect::new(0, 0, w, h))?; + self.render(tui)?; + Ok(()) + } + + fn render(&mut self, tui: &mut Tui) -> Result<()> { + tui.draw(|frame| { + if let [header, _body, footer, left, right, popup] = get_chunks(frame.area())[..] { + let component_areas = vec![ + header, // Title Bar + header, // FPS Counter + left, right, footer, popup, + ]; + for (component, area) in zip(self.components.iter_mut(), component_areas) { + if let Err(err) = component.draw(frame, area) { + let _ = self + .action_tx + .send(Action::Error(format!("Failed to draw: {err:?}"))); + } + } + }; + })?; + Ok(()) } } + +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 +} + +fn get_chunks(r: Rect) -> Vec { + let mut chunks: Vec = Vec::with_capacity(6); + + // Main sections + chunks.extend( + Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(3), + Constraint::Min(1), + Constraint::Length(3), + ]) + .split(r) + .to_vec(), + ); + + // Left/Right + chunks.extend( + Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(centered_rect(90, 90, chunks[1])) + .to_vec(), + ); + + // Popup + chunks.push(centered_rect(60, 25, r)); + + // Done + chunks +} + +pub fn build_command(app: &App, tool: &Tool) -> Task { + let cmd_path: PathBuf; + let mut cmd_args: Vec = Vec::new(); + let start_index: usize; + if tool.use_conemu { + cmd_path = app.config.conemu_path.clone(); + cmd_args.push(String::from("-new_console:n")); + cmd_args.push(tool.command.clone()); + start_index = 1; + } else { + cmd_path = PathBuf::from(tool.command.clone()); + start_index = 0; + } + if let Some(args) = &tool.args { + if args.len() > start_index { + args[start_index..].iter().for_each(|a| { + cmd_args.push(a.clone()); + }); + } + } + Task::Command(cmd_path, cmd_args) +} + +fn build_left_items(app: &App) -> Action { + let title = String::from("Tools"); + let labels = vec![String::new(), String::new()]; + let items = app + .list + .items + .iter() + .map(|tool| { + if tool.separator { + String::from("──────────────") + } else { + tool.name.clone() + } + }) + // ─ + .collect(); + Action::UpdateLeft(title, labels, items, 0) +} + +fn build_right_items(app: &App) -> Action { + let labels: Vec> = Vec::new(); + let items = app + .list + .items + .iter() + .map(|entry| { + vec![ + DVLine { + line_parts: vec![entry.name.clone()], + line_colors: vec![Color::Cyan], + }, + DVLine { + line_parts: vec![String::new()], + line_colors: vec![Color::Reset], + }, + DVLine { + line_parts: vec![entry.description.clone()], + line_colors: vec![Color::Reset], + }, + ] + }) + .collect(); + let start_index = 0; + Action::UpdateRight(labels, start_index, items) +} + +pub fn load_tools() -> Vec { + let exe_path = env::current_exe().expect("Failed to find main executable"); + let tool_config_path = exe_path.parent().unwrap().join("menu_entries"); + let mut entries: Vec = std::fs::read_dir(tool_config_path) + .expect("Failed to find any tool configs") + .map(|res| res.map(|e| e.path())) + .filter_map(Result::ok) + .collect(); + entries.sort(); + entries + .iter() + .map(|entry| { + let contents = fs::read_to_string(&entry).expect("Failed to read tool config file"); + let tool: Tool = toml::from_str(&contents).expect("Failed to parse tool config file"); + tool + }) + .collect() +} diff --git a/pe_menu/src/event.rs b/pe_menu/src/event.rs deleted file mode 100644 index 340e426..0000000 --- a/pe_menu/src/event.rs +++ /dev/null @@ -1,116 +0,0 @@ -// This file is part of Deja-vu. -// -// Deja-vu is free software: you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Deja-vu is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . -// -use std::time::Duration; - -use crossterm::event::{Event as CrosstermEvent, KeyEvent, MouseEvent}; -use futures::{FutureExt, StreamExt}; -use tokio::sync::mpsc; - -use crate::app::AppResult; - -/// Terminal events. -#[derive(Clone, Copy, Debug)] -pub enum Event { - /// Terminal tick. - Tick, - /// Key press. - Key(KeyEvent), - /// Mouse click/scroll. - Mouse(MouseEvent), - /// Terminal resize. - Resize(u16, u16), -} - -/// Terminal event handler. -#[allow(dead_code)] -#[derive(Debug)] -pub struct Handler { - /// Event sender channel. - sender: mpsc::UnboundedSender, - /// Event receiver channel. - receiver: mpsc::UnboundedReceiver, - /// Event handler thread. - handler: tokio::task::JoinHandle<()>, -} - -impl Handler { - /// Constructs a new instance of [`Handler`]. - /// - /// # Panics - /// - /// Will panic if `sender_clone ` doesn't unwrap - #[must_use] - pub fn new(tick_rate: u64) -> Self { - let tick_rate = Duration::from_millis(tick_rate); - let (sender, receiver) = mpsc::unbounded_channel(); - let sender_clone = sender.clone(); - let handler = tokio::spawn(async move { - let mut reader = crossterm::event::EventStream::new(); - let mut tick = tokio::time::interval(tick_rate); - loop { - let tick_delay = tick.tick(); - let crossterm_event = reader.next().fuse(); - tokio::select! { - () = sender_clone.closed() => { - break; - } - _ = tick_delay => { - sender_clone.send(Event::Tick).unwrap(); - } - Some(Ok(evt)) = crossterm_event => { - match evt { - CrosstermEvent::Key(key) => { - if key.kind == crossterm::event::KeyEventKind::Press { - sender_clone.send(Event::Key(key)).unwrap(); - } - }, - CrosstermEvent::Mouse(mouse) => { - sender_clone.send(Event::Mouse(mouse)).unwrap(); - }, - CrosstermEvent::Resize(x, y) => { - sender_clone.send(Event::Resize(x, y)).unwrap(); - }, - CrosstermEvent::FocusGained | CrosstermEvent::FocusLost | CrosstermEvent::Paste(_) => {}, - } - } - }; - } - }); - Self { - sender, - receiver, - handler, - } - } - - /// Receive the next event from the handler thread. - /// - /// This function will always block the current thread if - /// there is no data available and it's possible for more data to be sent. - /// - /// # Errors - /// - /// Will return error if a event is not found - pub async fn next(&mut self) -> AppResult { - self.receiver - .recv() - .await - .ok_or(Box::new(std::io::Error::new( - std::io::ErrorKind::Other, - "This is an IO error", - ))) - } -} diff --git a/pe_menu/src/handler.rs b/pe_menu/src/handler.rs deleted file mode 100644 index fed2d63..0000000 --- a/pe_menu/src/handler.rs +++ /dev/null @@ -1,61 +0,0 @@ -// This file is part of Deja-vu. -// -// Deja-vu is free software: you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Deja-vu is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . -// -use crate::app::{App, QuitReason}; -use crossterm::event::{KeyCode, KeyEvent}; - -/// Handles the key events and updates the state of [`App`]. -pub fn handle_key_events(key_event: KeyEvent, app: &mut App) { - match key_event.code { - KeyCode::F(5) => app.quit(QuitReason::Exit), - KeyCode::Char('p' | 'P') => app.quit(QuitReason::Poweroff), - KeyCode::Char('r' | 'R') => app.quit(QuitReason::Restart), - KeyCode::Char('t' | 'T') => app.open_terminal(), - KeyCode::Up => { - if app.popup.is_none() { - app.main_menu.previous(); - if let Some(e) = app.main_menu.get_selected() { - if e.separator { - // Skip over separators - app.main_menu.previous(); - } - } - } - } - KeyCode::Down => { - if app.popup.is_none() { - app.main_menu.next(); - if let Some(e) = app.main_menu.get_selected() { - if e.separator { - // Skip over separators - app.main_menu.next(); - } - } - } - } - KeyCode::Enter => { - if app.popup.is_some() { - // Clear popup and return to main menu - app.popup = None; - } else { - app.run_tool(); - } - } - KeyCode::Esc | KeyCode::Char('q' | 'Q') => { - app.popup = None; - } - _ => {} - } -} diff --git a/pe_menu/src/main.rs b/pe_menu/src/main.rs index b7fab7a..8bcfda9 100644 --- a/pe_menu/src/main.rs +++ b/pe_menu/src/main.rs @@ -13,40 +13,21 @@ // You should have received a copy of the GNU General Public License // along with Deja-vu. If not, see . // -use pe_menu::app::{App, AppResult}; -use pe_menu::event::{Event, Handler}; -use pe_menu::handler::handle_key_events; -use pe_menu::tui::Tui; -use ratatui::backend::CrosstermBackend; -use ratatui::Terminal; -use std::io; +use clap::Parser; +use color_eyre::Result; +use core; + +use crate::app::App; + +mod app; #[tokio::main] -async fn main() -> AppResult<()> { - // Create an application. - let mut app = App::new(); +async fn main() -> Result<()> { + core::errors::init()?; + core::logging::init()?; - // Initialize the terminal user interface. - let backend = CrosstermBackend::new(io::stderr()); - let terminal = Terminal::new(backend)?; - let events = Handler::new(250); - let mut tui = Tui::new(terminal, events); - tui.init()?; - - // Start the main loop. - while app.running { - // Render the user interface. - tui.draw(&mut app)?; - // Handle events. - match tui.events.next().await? { - Event::Tick => app.tick(), - Event::Key(key_event) => handle_key_events(key_event, &mut app), - Event::Mouse(_) | Event::Resize(_, _) => {} - } - } - - // Exit the user interface. - tui.exit()?; - app.exit()?; + let args = core::cli::Cli::parse(); + let mut app = App::new(args.tick_rate, args.frame_rate)?; + app.run().await?; Ok(()) } diff --git a/pe_menu/src/tui.rs b/pe_menu/src/tui.rs deleted file mode 100644 index 313108c..0000000 --- a/pe_menu/src/tui.rs +++ /dev/null @@ -1,111 +0,0 @@ -// This file is part of Deja-vu. -// -// Deja-vu is free software: you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Deja-vu is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . -// -use crate::app::{App, AppResult}; -use crate::event::Handler; -use crate::ui; -use crossterm::event::{DisableMouseCapture, EnableMouseCapture}; -use crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}; -use ratatui::backend::Backend; -use ratatui::Terminal; -use std::io; -use std::panic; - -/// Representation of a terminal user interface. -/// -/// It is responsible for setting up the terminal, -/// initializing the interface and handling the draw events. -#[derive(Debug)] -pub struct Tui { - /// Interface to the Terminal. - terminal: Terminal, - /// Terminal event handler. - pub events: Handler, -} - -impl Tui { - /// Constructs a new instance of [`Tui`]. - pub fn new(terminal: Terminal, events: Handler) -> Self { - Self { terminal, events } - } - - /// Initializes the terminal interface. - /// - /// It enables the raw mode and sets terminal properties. - /// - /// # Errors - /// - /// Will return error if `enable_raw_mode` fails - /// - /// # Panics - /// - /// Will panic if `reset` fails - pub fn init(&mut self) -> AppResult<()> { - terminal::enable_raw_mode()?; - crossterm::execute!(io::stderr(), EnterAlternateScreen, EnableMouseCapture)?; - - // Define a custom panic hook to reset the terminal properties. - // This way, you won't have your terminal messed up if an unexpected error happens. - let panic_hook = panic::take_hook(); - panic::set_hook(Box::new(move |panic| { - Self::reset().expect("failed to reset the terminal"); - panic_hook(panic); - })); - - self.terminal.hide_cursor()?; - self.terminal.clear()?; - Ok(()) - } - - /// [`Draw`] the terminal interface by [`rendering`] the widgets. - /// - /// [`Draw`]: ratatui::Terminal::draw - /// [`rendering`]: crate::ui::render - /// - /// # Errors - /// - /// Will return error if `draw` fails - pub fn draw(&mut self, app: &mut App) -> AppResult<()> { - self.terminal.draw(|frame| ui::render(app, frame))?; - Ok(()) - } - - /// Resets the terminal interface. - /// - /// This function is also used for the panic hook to revert - /// the terminal properties if unexpected errors occur. - /// - /// # Errors - /// - /// Will return error if `disable_raw_mode` fails - fn reset() -> AppResult<()> { - terminal::disable_raw_mode()?; - crossterm::execute!(io::stderr(), LeaveAlternateScreen, DisableMouseCapture)?; - Ok(()) - } - - /// Exits the terminal interface. - /// - /// It disables the raw mode and reverts back the terminal properties. - /// - /// # Errors - /// - /// Will return error if either `reset` or `show_cursor` fails - pub fn exit(&mut self) -> AppResult<()> { - Self::reset()?; - self.terminal.show_cursor()?; - Ok(()) - } -} diff --git a/pe_menu/src/ui.rs b/pe_menu/src/ui.rs deleted file mode 100644 index bdfdedf..0000000 --- a/pe_menu/src/ui.rs +++ /dev/null @@ -1,127 +0,0 @@ -// This file is part of Deja-vu. -// -// Deja-vu is free software: you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Deja-vu is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Deja-vu. If not, see . -// -use crate::app::App; -use ratatui::{ - layout::{Constraint, Direction, Layout, Rect}, - style::{Color, Style, Stylize}, - text::{Line, Span}, - widgets::{Block, Borders, Clear, HighlightSpacing, List, ListItem, Padding, Paragraph, Wrap}, - Frame, -}; - -/// Renders the user interface widgets. -pub fn render(app: &mut App, frame: &mut Frame) { - let chunks = Layout::default() - .direction(Direction::Vertical) - .constraints([ - Constraint::Length(3), - Constraint::Min(1), - Constraint::Length(3), - ]) - .split(frame.size()); - - // Title Block - let title_text = Span::styled("WizardKit: PE Menu", Style::default().fg(Color::LightCyan)); - let title = Paragraph::new(Line::from(title_text).centered()) - .block(Block::default().borders(Borders::ALL)); - frame.render_widget(title, chunks[0]); - - // Main Block - let main_chunk = centered_rect(65, 90, chunks[1]); - render_main_pane(frame, app, main_chunk); - - // Bottom Block - let footer_text = Span::styled( - "(Enter) to select / (p) to poweroff / (r) to restart / (t) for terminal", - Style::default().fg(Color::DarkGray), - ); - let footer = Paragraph::new(Line::from(footer_text).centered()) - .block(Block::default().borders(Borders::ALL)); - frame.render_widget(footer, chunks[2]); - - // Popup blocks - if app.popup.is_some() { - render_popup_pane(frame, app); - } -} - -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 -} - -fn render_main_pane(frame: &mut Frame, app: &mut App, chunk: Rect) { - let mut list_items = Vec::::new(); - for entry in &app.main_menu.items { - let text = if entry.separator { - if entry.name.is_empty() { - String::from("....................") - } else { - entry.name.clone() - } - } else { - entry.name.clone() - }; - list_items.push(ListItem::new(format!(" {text}\n\n\n"))); - } - let list = List::new(list_items) - .block( - Block::default() - .borders(Borders::ALL) - .padding(Padding::new(20, 20, 5, 5)), - ) - .highlight_spacing(HighlightSpacing::Always) - .highlight_style(Style::new().green().bold()) - .highlight_symbol(" --> ") - .repeat_highlight_symbol(false); - frame.render_stateful_widget(list, chunk, &mut app.main_menu.state); -} - -fn render_popup_pane(frame: &mut Frame, app: &mut App) { - let popup_block = Block::default() - .borders(Borders::ALL) - .style(Style::default().red().bold()); - if let Some(popup) = &app.popup { - let scan_paragraph = Paragraph::new(vec![ - Line::from(Span::raw(&popup.title)), - Line::default(), - Line::from(Span::raw(&popup.body)), - ]) - .block(popup_block) - .centered() - .wrap(Wrap { trim: false }); - let area = centered_rect(60, 25, frame.size()); - frame.render_widget(Clear, area); - frame.render_widget(scan_paragraph, area); - } -}