From 007398c57646530cd461eef1ed2ef84cb2e56f8d Mon Sep 17 00:00:00 2001 From: YK Date: Tue, 16 Jul 2024 05:09:29 +0300 Subject: [PATCH] squash old --- .gitignore | 1 + Cargo.lock | 1092 +++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 21 + data.db | 10 + src/app.rs | 206 +++++++++ src/app/filter.rs | 19 + src/app/main.rs | 112 +++++ src/app/td.rs | 28 ++ src/error.rs | 29 ++ src/main.rs | 31 ++ src/prelude.rs | 4 + src/serial.rs | 51 +++ src/task.rs | 76 ++++ src/tui.rs | 28 ++ src/utils.rs | 76 ++++ 15 files changed, 1784 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 data.db create mode 100644 src/app.rs create mode 100644 src/app/filter.rs create mode 100644 src/app/main.rs create mode 100644 src/app/td.rs create mode 100644 src/error.rs create mode 100644 src/main.rs create mode 100644 src/prelude.rs create mode 100644 src/serial.rs create mode 100644 src/task.rs create mode 100644 src/tui.rs create mode 100644 src/utils.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..2dec040 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1092 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cc" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.5", +] + +[[package]] +name = "color-eyre" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "compact_str" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "ryu", + "static_assertions", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "either" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "lru" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.5", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "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.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" +dependencies = [ + "bitflags", + "cassowary", + "compact_str", + "crossterm", + "itertools 0.12.1", + "lru", + "paste", + "stability", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width", +] + +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "stability" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ff9eaf853dec4c8802325d8b6d3dffa86cc707fd7a1a4cdbf416e13b061787a" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strum" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "todox" +version = "0.1.0" +dependencies = [ + "chrono", + "color-eyre", + "crossterm", + "dotenvy", + "itertools 0.13.0", + "lazy_static", + "nom", + "ratatui", + "serde", + "serde_json", + "strum", + "tui-input", + "tui-textarea", + "unescape", + "uuid", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + +[[package]] +name = "tui-input" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3e785f863a3af4c800a2a669d0b64c879b538738e352607e2624d03f868dc01" +dependencies = [ + "crossterm", + "unicode-width", +] + +[[package]] +name = "tui-textarea" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e38ced1f941a9cfc923fbf2fe6858443c42cc5220bfd35bdd3648371e7bd8e" +dependencies = [ + "crossterm", + "ratatui", + "unicode-width", +] + +[[package]] +name = "unescape" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccb97dac3243214f8d8507998906ca3e2e0b900bf9bf4870477f125b82e68f6e" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-truncate" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5fbabedabe362c618c714dbefda9927b5afc8e2a8102f47f081089a9019226" +dependencies = [ + "itertools 0.12.1", + "unicode-width", +] + +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + +[[package]] +name = "uuid" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +dependencies = [ + "getrandom", + "rand", + "uuid-macro-internal", +] + +[[package]] +name = "uuid-macro-internal" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9881bea7cbe687e36c9ab3b778c36cd0487402e270304e8b1296d5085303c1a2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.5", +] + +[[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-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", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "zerocopy" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2aabc13 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "todox" +version = "0.1.0" +edition = "2021" + +[dependencies] +chrono = "0.4.38" +color-eyre = "0.6.3" +crossterm = "0.27.0" +dotenvy = "0.15.7" +itertools = "0.13.0" +lazy_static = "1.4.0" +nom = "7.1.3" +ratatui = "0.26.3" +serde = { version = "1.0.203", features = ["derive"] } +serde_json = "1.0.117" +strum = { version = "0.26.2", features = ["derive"] } +tui-input = "0.8.0" +tui-textarea = "0.4.0" +unescape = "0.1.0" +uuid = { version = "1.8.0", features = ["fast-rng", "macro-diagnostics", "v4"] } diff --git a/data.db b/data.db new file mode 100644 index 0000000..ae6f4f9 --- /dev/null +++ b/data.db @@ -0,0 +1,10 @@ +2c8bcfa9-df2c-4cfc-a896-74bba8351a90 | Lorem ipsum | something something 1 | p | never | o +93f34fb9-cf34-469f-906f-74c39bc64929 | Lorem1 ipsum | something something 2 | p | 1721684917 | o +9097a569-4429-4710-9cbf-00b9c796f130 | Lorem2 ipsum | something something 3 | d | 1718574517 | o +e6038f70-4afc-4f83-9125-936e2d8b9b31 | Lorem3 ipsum | something\nsomething 4 | c | 1718488117 | o +1e765b2c-012e-4204-bf7d-d0fb891ad85b | Lorem3 ipsum | something\nsomething 5 | d | never | o +513dadfa-0750-49b3-b22d-9e58d44c63f8 | Lorem4 ipsum | something\r\nsomething 6 | p | never | o +02020217-44a5-4865-9af2-68320eee37be | Lorem5 ipsum | something\tsomething 7 | p | never | o +215a04a0-0973-4b5d-a144-c01ddbd33a53 | Lorem6 ipsum | something something 8 | p | never | o +54e9e149-2c04-4786-bd6b-5df28dc50906 | Lorem7 ipsum | something something 9 | p | never | o +bef1588a-94ee-45f9-be54-db90d2a04751 | Lorem8 ipsum | something something 10 | p | never | o diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..33fad39 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,206 @@ + +use std::collections::HashSet; + +use crossterm::event::{ self, Event, KeyCode, KeyEvent, KeyEventKind }; +use ratatui::prelude::*; + +use color_eyre::eyre::{ Result, WrapErr }; +use strum::{ Display, EnumCount, EnumIter, FromRepr }; + +use crate::prelude::*; + +mod td; +mod filter; +mod main; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Cursor { + row: usize, + col: usize, +} + +impl Cursor { + fn +} + +#[derive(Debug, Default)] +pub struct App { + pub screen: Screen, + pub mode: Mode, + pub tasks: TaskList, + pub selection: HashSet, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum Screen { + Main { inner: MainScreen, cursor: Cursor }, + Exiting, +} + +impl Default for Screen { + fn default () -> Self { + Self::Main(MainScreen::default()) + } +} + +#[derive(Debug, Default, PartialEq, Eq, FromRepr, EnumCount, EnumIter, Clone, Copy, Display)] +pub enum MainScreen { + #[default] + Pending, + Done, + Cancelled, + Recurring, + Expired, + All, +} + + + + +#[derive(Debug, Default)] +pub enum Mode { + #[default] + Normal, + Select, + Add, + Edit, +} + + +pub const UI_COLUMNS: usize = 6; + +impl App { + + pub fn run (&mut self, term: &mut Tui) -> Result<()> { + loop { + term.draw(|frame| self.render_frame(frame))?; + let exiting = self.handle_events().wrap_err("handle events failed")?; + + if exiting { break; } + } + + Ok(()) + } + + fn render_frame (&self, frame: &mut Frame) { + frame.render_widget(self, frame.size()) + } + + fn handle_events (&mut self) -> Result { + match event::read()? { + Event::Key(key_event) if key_event.kind == KeyEventKind::Press => { + self.handle_key_event(key_event) + .wrap_err_with(|| format!("handling key event failed:\n{key_event:#?}")) + } + _ => Ok(false) + } + } + + fn handle_key_event (&mut self, k: KeyEvent) -> Result { + + + match &self.screen { + Screen::Main (screen) => { + match self.mode { + Mode::Normal => { + match k.code { + KeyCode::Char('q') => self.screen = Screen::Exiting, + KeyCode::Tab => { + let next = *screen as usize + 1; + self.cursor = (0, 0); + self.selection.clear(); + self.screen = Screen::Main(MainScreen::from_repr(next % MainScreen::COUNT).unwrap_or_default()); + }, + KeyCode::BackTab => { + let prev = (*screen as usize).checked_sub(1).unwrap_or(MainScreen::COUNT - 1); + self.cursor = (0, 0); + self.selection.clear(); + self.screen = Screen::Main(MainScreen::from_repr(prev).unwrap_or_default()); + }, + KeyCode::Up => { + if self.cursor.0 > 0 { + self.cursor.0 -= 1; + } + }, + KeyCode::Left => { + if self.cursor.1 > 0 { + self.cursor.1 -= 1; + } + }, + KeyCode::Down => { + self.cursor.0 += 1; + }, + KeyCode::Right => { + self.cursor.1 += 1; + }, + + KeyCode::Char(' ') => { + if self.selection.contains(&self.cursor.0) { + self.selection.remove(&self.cursor.0); + } else { + self.selection.insert(self.cursor.0); + } + } + + _ => () + } + }, + _ => { + + } + } + }, + Screen::Exiting => { + match k.code { + KeyCode::Enter | KeyCode::Char('y') | KeyCode::Char('q') => return Ok(true), + KeyCode::Esc | KeyCode::Char('n') => self.screen = Screen::Main(MainScreen::default()), + _ => () + } + } + } + + Ok(false) + } +} + + +impl Widget for &App { + fn render (self, area: Rect, buf: &mut Buffer) { + self.screen.render(self, area, buf) + } +} + + +impl Screen { + fn render (&self, app: &App, area: Rect, buf: &mut Buffer) -> () { + match self { + Self::Main(ms) => main::screen(app, *ms, area, buf), + _ => () + } + } +} + + + + +fn centered_rect (px: u16, py: u16, r: Rect) -> Rect { + // Cut the given rectangle into three vertical pieces + let popup_layout = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Percentage((100 - py) / 2), + Constraint::Percentage(py), + Constraint::Percentage((100 - py) / 2), + ]) + .split(r); + + // Then cut the middle vertical piece into three width-wise pieces + Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Percentage((100 - px) / 2), + Constraint::Percentage(px), + Constraint::Percentage((100 - px) / 2), + ]) + .split(popup_layout[1])[1] // Return the middle chunk +} diff --git a/src/app/filter.rs b/src/app/filter.rs new file mode 100644 index 0000000..13a1bdd --- /dev/null +++ b/src/app/filter.rs @@ -0,0 +1,19 @@ +use chrono::Utc; +use uuid::Uuid; + +use crate::prelude::*; + +use super::MainScreen; + +impl MainScreen { + pub fn task_filter (self) -> impl Fn(&&Task) -> bool { + move |task: &&Task| match self { + Self::All => true, + Self::Cancelled => task.status == TaskStatus::Cancelled, + Self::Pending => task.status == TaskStatus::Pending, + Self::Done => task.status == TaskStatus::Done, + Self::Expired => task.expiring_at.is_some_and(|dt| Utc::now() > dt), + Self::Recurring => std::mem::discriminant(&task.kind) == std::mem::discriminant(&TaskKind::Recurring { parent: Uuid::default() }), + } + } +} diff --git a/src/app/main.rs b/src/app/main.rs new file mode 100644 index 0000000..ade0b0e --- /dev/null +++ b/src/app/main.rs @@ -0,0 +1,112 @@ +use crate::prelude::*; +use super::MainScreen; + +use ratatui::{ + prelude::*, + widgets::{ + block::*, + Block, Borders, List, ListItem, Paragraph + } +}; +use utils::truncate_with_ellipsis as tc; +use itertools::Itertools; +use strum::IntoEnumIterator; + +pub fn screen (app: &App, ms: MainScreen, area: Rect, buf: &mut Buffer) { + let layout = Layout::default() + .direction(Direction::Vertical) + .constraints(vec![ + Constraint::Length(3), + Constraint::Min(12), + Constraint::Length(3), + ]) + .split(area); + + Paragraph::new(Line::from( + MainScreen::iter().map(|e| { + let text = format!(" {e} "); + if e == ms { + Span::styled(text, Style::default().fg(Color::White).bg(Color::from_u32(0x550055))) + } else { + Span::styled(text, Style::default().fg(Color::White)) + } + }) + .intersperse(Span::styled(" | ", Style::default().fg(Color::White))) + .collect_vec() + ).centered()).block(Block::default().borders(Borders::BOTTOM).padding(Padding::top(1))).render(layout[0], buf); + let mut list_items = Vec::::new(); + + + let w = area.width; + + macro_rules! wx { + ($i: literal) => { + (w / 100).max(1) as usize * $i + }; + } + + let cw: [(usize, &'static str, super::td::TdFn); 6] = [ + (wx!(4), "#", super::td::id), + (wx!(15), "Title", super::td::name), + (wx!(40), "Description", super::td::description), + (wx!(15), "Expires", super::td::expiry), + (wx!(10), "Status", super::td::status), + (wx!(10), "Kind", super::td::kind) + ]; + + let th = |col: usize| if col == app.cursor.1 % cw.len() { Style::default().bold().bg(Color::from_u32(0x253325)) } else { Style::default() }; + + let delimiter = || Span::styled("|", Style::default()); + + let header: Vec = cw.iter().enumerate().map(|(idx, (width, h, _))| Span::styled(format!("{: ^width$}", h, width = width), th(idx))).intersperse_with(delimiter).collect_vec(); + + let iterator = || app.tasks.iter().filter(ms.task_filter()); + let item_count = iterator().count(); + + let td = |row: usize, col: usize| match (row, col) { + (r, c) if r == app.cursor.0 % item_count && c == app.cursor.1 % cw.len() => Style::default().bg(Color::from_u32(0x007700)), + (r, _) if r == app.cursor.0 % item_count => Style::default().bg(Color::from_u32(0x005500)), + // (_, c) if c == app.cursor.1 % cw.len() => Style::default().bg(Color::from_u32(0x001500)), + _ => Style::default() + }; + + let tr = |row: usize| return move |col: usize| td(row, col); + + list_items.push(ListItem::new(Line::from(header))); + list_items.push(ListItem::new(Line::from(Span::from("-".repeat(w as usize))))); + + for s @ (idx, _task) in iterator().enumerate() { + let idx = idx % item_count; + let td = tr(idx); + let bg = if app.selection.contains(&idx) { + Color::from_u32(0x000044) + } else if idx == app.cursor.0 % item_count { + Color::from_u32(0x002200) + } else { + Color::default() + }; + let line = cw.iter().enumerate().map(|(idx, (width, _, content))| Span::styled(format!("{: ^width$}", tc(&content(s), *width), width = width), td(idx))).intersperse_with(delimiter).collect_vec(); + list_items.push(ListItem::new(Line::from(line).bg(bg))); + } + + let list = List::new(list_items); + Widget::render(list, layout[1], buf); + + + let footer = Layout::default() + .direction(Direction::Horizontal) + .constraints(vec![ + Constraint::Percentage(40), + Constraint::Percentage(40), + Constraint::Fill(1) + ]) + .split(layout[2]); + + if !app.selection.is_empty() { + let total = item_count; + let selected = app.selection.len(); + + Paragraph::new(Line::from(Span::styled(format!("[{selected}/{total}]"), Style::default()))).block(Block::default().borders(Borders::ALL)).render(footer[2], buf); + } + +} diff --git a/src/app/td.rs b/src/app/td.rs new file mode 100644 index 0000000..10f01aa --- /dev/null +++ b/src/app/td.rs @@ -0,0 +1,28 @@ +use std::borrow::Cow; +use chrono::Utc; + +use crate::prelude::*; + +pub type TdFn<'a> = fn((usize, &'a Task)) -> Cow<'a, str>; +// +// kind of ugly but don't want to think about it rn too much +pub fn id <'a> ((idx, _task): (usize, &'a Task)) -> Cow<'a, str> { + return Cow::Owned(idx.to_string()); +} +pub fn name <'a> ((_idx, task): (usize, &'a Task)) -> Cow<'a, str> { + return Cow::Borrowed(&task.name); +} +pub fn description <'a> ((_idx, task): (usize, &'a Task)) -> Cow<'a, str> { + return Cow::Owned(task.description.replace(['\n', '\t', '\r'], " ")); +} +pub fn expiry <'a> ((_idx, task): (usize, &'a Task)) -> Cow<'a, str> { + return Cow::Owned(task.expiring_at.map(|e| utils::dt_format(e - Utc::now())).unwrap_or_default()); +} +pub fn status <'a> ((_idx, task): (usize, &'a Task)) -> Cow<'a, str> { + return Cow::Owned(String::from("tl")); + // return Cow::Owned(task.expiring_at.map(|e| e.to_rfc2822()).unwrap_or_default()); +} +pub fn kind <'a> ((_idx, task): (usize, &'a Task)) -> Cow<'a, str> { + return Cow::Owned(String::from("kind")); + // return Cow::Owned(task.expiring_at.map(|e| e.to_rfc2822()).unwrap_or_default()); +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..f243d02 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,29 @@ +use std::panic; + +use color_eyre::{ config::HookBuilder, eyre }; + +use crate::tui; + +/// This replaces the standard color_eyre panic and error hooks with hooks that +/// restore the terminal before printing the panic or error. +pub fn install_hooks () -> color_eyre::Result<()> { + let (panic_hook, eyre_hook) = HookBuilder::default().into_hooks(); + + // convert from a color_eyre PanicHook to a standard panic hook + let panic_hook = panic_hook.into_panic_hook(); + panic::set_hook(Box::new(move |panic_info| { + tui::restore_raw().unwrap(); + panic_hook(panic_info); + })); + + // convert from a color_eyre EyreHook to a eyre ErrorHook + let eyre_hook = eyre_hook.into_eyre_hook(); + eyre::set_hook(Box::new( + move |error: &(dyn std::error::Error + 'static)| { + tui::restore_raw().unwrap(); + eyre_hook(error) + }, + ))?; + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..75cac3d --- /dev/null +++ b/src/main.rs @@ -0,0 +1,31 @@ +#![feature(variant_count)] +#![feature(let_chains)] + +use color_eyre::Result; + +pub mod utils; +mod prelude; + +mod tui; +mod app; + +mod error; +mod task; +mod serial; + +use prelude::*; +use serial::read_db; + +fn main() -> Result<()> { + error::install_hooks()?; + let mut term = tui::init()?; + let mut app = App::default(); + + app.tasks = read_db(); + + app.run(&mut term)?; + + tui::restore(term)?; + + Ok(()) +} diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 0000000..9d095ca --- /dev/null +++ b/src/prelude.rs @@ -0,0 +1,4 @@ +pub use crate::tui::{ Tui }; +pub use crate::app::{ App }; +pub use crate::task::{ Task, TaskKind, TaskStatus, TaskList }; +pub use crate::utils; diff --git a/src/serial.rs b/src/serial.rs new file mode 100644 index 0000000..430bbdf --- /dev/null +++ b/src/serial.rs @@ -0,0 +1,51 @@ +use chrono::{ DateTime, Utc }; +use itertools::Itertools; +use uuid::Uuid; + +use crate::prelude::*; +use std::fs::read_to_string; + + +pub fn read_db () -> TaskList { + // @todo read from argument or config file + let file = read_to_string("data.db").unwrap(); + + file.lines().filter_map(|line| { + let [id, name, description, status, expiration, kind] = line.split(" | ").collect_vec()[..] else { + return None; + }; + + let id = Uuid::parse_str(id).ok()?; + + Some(Task { + id, + name: name.to_owned(), + description: unescape::unescape(description).unwrap_or_else(|| description.to_owned()), + status: TaskStatus::from_data_part(status), + expiring_at: parse_expiration_part(expiration), + kind: TaskKind::from_data_part(kind), + ..Default::default() + }) + }) + .sorted_unstable_by(|at, bt| at.expiring_at.unwrap_or(DateTime::::MAX_UTC).cmp(&bt.expiring_at.unwrap_or(DateTime::::MAX_UTC))) + .collect() +} + +pub fn parse_expiration_part (part: &str) -> Option> { + if part == "never" { return None; } + + DateTime::from_timestamp(part.parse().ok()?, 0) +} + + + +#[cfg(test)] +mod test { + use super::read_db; + + #[test] + fn basic () { + let list = read_db(); + println!("{:?}", list); + } +} diff --git a/src/task.rs b/src/task.rs new file mode 100644 index 0000000..f84c0cc --- /dev/null +++ b/src/task.rs @@ -0,0 +1,76 @@ +use std::collections::HashMap; + +use chrono::{ DateTime, Duration, NaiveDateTime, Utc }; +use uuid::Uuid; + +pub type TaskList = Vec; +pub type EnabledReccurentTemplated = Vec; + +#[derive(Debug, Clone, Default)] +pub struct Task { + pub id: Uuid, + pub name: String, + pub description: String, + pub kind: TaskKind, + + pub status: TaskStatus, + pub expiring_at: Option>, +} + +#[derive(Debug, Clone)] +pub struct RecurringTaskTemplate { + pub template_id: Uuid, + pub name: String, + pub description: String, + pub period: RecurrenceKind, + pub enabled: bool, + + pub status: TaskStatus, + pub expiring_duration: Option, +} + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub enum TaskStatus { + Done, + Cancelled, + #[default] + Pending, +} + +impl TaskStatus { + pub fn from_data_part (part: &str) -> Self { + match part.chars().next() { + Some('p') => Self::Pending, + Some('c') => Self::Cancelled, + Some('d') => Self::Done, + _ => Self::Pending // or should we panic here? + } + } +} + +#[derive(Debug, Clone, Default, PartialEq)] +pub enum TaskKind { + #[default] + Oneshot, + Recurring { + parent: Uuid + }, +} + +impl TaskKind { + pub fn from_data_part (_part: &str) -> Self { + Self::Oneshot + } +} + +/// Notes on parsing RecurrenceKind: +/// @TODO +#[derive(Debug, Clone)] +pub enum RecurrenceKind { + Simple (Duration), + WeekDays (Vec), + MonthDays (Vec), + YearDays (Vec), + Hours (Vec), + Composite (Vec), +} diff --git a/src/tui.rs b/src/tui.rs new file mode 100644 index 0000000..93be368 --- /dev/null +++ b/src/tui.rs @@ -0,0 +1,28 @@ +use std::io::{ self, stderr, Stderr }; + +use crossterm::{ event::{DisableMouseCapture, EnableMouseCapture}, execute, terminal::* }; +use ratatui::prelude::*; + +pub type Tui = Terminal>; + +pub fn init () -> io::Result { + enable_raw_mode()?; + let mut stderr = io::stderr(); + execute!(stderr, EnterAlternateScreen, EnableMouseCapture)?; + + let backend = CrosstermBackend::new(stderr); + Terminal::new(backend) +} + +pub fn restore (mut term: Tui) -> io::Result<()> { + disable_raw_mode()?; + execute!(term.backend_mut(), LeaveAlternateScreen, DisableMouseCapture)?; + term.show_cursor()?; + Ok(()) +} + +pub fn restore_raw () -> io::Result<()> { + disable_raw_mode()?; + execute!(stderr(), LeaveAlternateScreen, DisableMouseCapture)?; + Ok(()) +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..fb4462a --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,76 @@ +use chrono::TimeDelta; + +/// Stolen from `cargo` crate +pub fn truncate_with_ellipsis (s: &str, max_width: usize) -> String { + // We should truncate at grapheme-boundary and compute character-widths, + // yet the dependencies on unicode-segmentation and unicode-width are + // not worth it. + let mut chars = s.chars(); + let mut prefix = (&mut chars).take(max_width - 1).collect::(); + if chars.next().is_some() { + prefix.push('…'); + } + prefix +} + +/// Take two biggest time units we can fit/fill the delta in (years/months, months/days, +/// days/hours, hours/minutes, minutes/seconds, seconds), format them into a string +pub fn dt_format (mut delta: TimeDelta) -> String { + let mut parts = vec![]; + + let expired = delta.num_seconds().is_negative(); + delta = delta.abs(); + + 'fmt : { + let years = delta.num_days() / 365; + if years > 0 { + delta -= TimeDelta::days(years * 365); + parts.push(format!("{years}y")); + } + + let months = delta.num_days() / 30; + if months > 0 { + delta -= TimeDelta::days(months * 30); + parts.push(format!("{months}mo")); + if parts.len() == 2 { break 'fmt; } + } + + let days = delta.num_days(); + if days > 0 { + delta -= TimeDelta::days(days); + parts.push(format!("{days}d")); + if parts.len() == 2 { break 'fmt; } + } + + let hours = delta.num_hours(); + if hours > 0 { + delta -= TimeDelta::hours(hours); + parts.push(format!("{hours}h")); + if parts.len() == 2 { break 'fmt; } + } + + let minutes = delta.num_minutes(); + if minutes > 0 { + delta -= TimeDelta::minutes(minutes); + parts.push(format!("{minutes}m")); + if parts.len() == 2 { break 'fmt; } + } + + let seconds = delta.num_seconds(); + if seconds > 0 { + delta -= TimeDelta::seconds(seconds); + parts.push(format!("{seconds}s")); + if parts.len() == 2 { break 'fmt; } + } + + if parts.len() == 0 { + parts.push(String::from("now")); + } + } + + if expired { + format!("{} ago", parts.join(" ")) + } else { + format!("in {}", parts.join(" ")) + } +}