From 55befc91a0e76cbb40ab9e73d88dd6697b6540a7 Mon Sep 17 00:00:00 2001 From: Dheepak Krishnamurthy Date: Tue, 21 Jul 2020 23:22:01 -0600 Subject: [PATCH] Initial commit --- .gitignore | 1 + Cargo.lock | 1028 +++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 25 ++ LICENSE | 21 ++ src/app.rs | 383 +++++++++++++++++++ src/main.rs | 83 +++++ src/util.rs | 96 +++++ 7 files changed, 1637 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 src/app.rs create mode 100644 src/main.rs create mode 100644 src/util.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..a7e3c6a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1028 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "addr2line" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "arc-swap" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "autocfg" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" + +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" + +[[package]] +name = "backtrace" +version = "0.3.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46254cf2fdcdf1badb5934448c1bcbe046a56537b3987d96c51a7afc5d03f293" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "chrono" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c74d84029116787153e02106bf53e66828452a4b325cc8652b788b5967c0a0b6" +dependencies = [ + "num-integer", + "num-traits", + "time", +] + +[[package]] +name = "clap" +version = "2.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "crossterm" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5750773d74a7dc612eac2ded3f55e9cdeeaa072210cd17c0192aedb48adb3618" +dependencies = [ + "bitflags", + "crossterm_winapi 0.5.1", + "lazy_static", + "libc", + "mio 0.6.22", + "parking_lot", + "signal-hook", + "winapi 0.3.9", +] + +[[package]] +name = "crossterm" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f4919d60f26ae233e14233cc39746c8c8bb8cd7b05840ace83604917b51b6c7" +dependencies = [ + "bitflags", + "crossterm_winapi 0.6.1", + "lazy_static", + "libc", + "mio 0.7.0", + "parking_lot", + "signal-hook", + "winapi 0.3.9", +] + +[[package]] +name = "crossterm_winapi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8777c700901e2d5b50c406f736ed6b8f9e43645c7e104ddb74f8bc42b8ae62f6" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "crossterm_winapi" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057b7146d02fb50175fd7dbe5158f6097f33d02831f43b4ee8ae4ddf67b68f5c" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "darling" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49fc76d30c96cc0bdc8b966968e6535d900f3e42c56204d355192a670d989c6e" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d844ad185d7f9bfd072914584649741768151c4131f6ae59f282889f7a1e450" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2 0.3.8", + "quote 0.5.2", + "syn 0.13.11", +] + +[[package]] +name = "darling_macro" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280207f9bd6f6fd58acd08ed722fb9a75412ad9b1fd9b6a8fbfc55410aca2c2c" +dependencies = [ + "darling_core", + "quote 0.5.2", + "syn 0.13.11", +] + +[[package]] +name = "derive_builder" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583a8f76cd41ae6303aca0db4539b90b4fcb289f75467d0c3905781dc670621b" +dependencies = [ + "darling", + "derive_builder_core", + "proc-macro2 0.3.8", + "quote 0.5.2", + "syn 0.13.11", +] + +[[package]] +name = "derive_builder_core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb4e6b5fb126caa298af7f9b9719ad6301eb7dd1613fd7543a4e935cef46c07" +dependencies = [ + "darling", + "proc-macro2 0.3.8", + "quote 0.5.2", + "syn 0.13.11", +] + +[[package]] +name = "failure" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +dependencies = [ + "proc-macro2 1.0.19", + "quote 1.0.7", + "syn 1.0.35", + "synstructure", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "getrandom" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" + +[[package]] +name = "hermit-abi" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" +dependencies = [ + "libc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "itoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[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.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7d4bd64732af4bf3a67f367c27df8520ad7e230c5817b8ff485864d80242b9" + +[[package]] +name = "lock_api" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0f75932c1f6cfae3c04000e40114adf955636e19040f9c0a2c380702aa1c7f" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.6.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" +dependencies = [ + "cfg-if", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow 0.2.1", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9971bc8349a361217a8f2a41f5d011274686bd4436465ba51730921039d7fb" +dependencies = [ + "lazy_static", + "libc", + "log", + "miow 0.3.5", + "ntapi", + "winapi 0.3.9", +] + +[[package]] +name = "miow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "miow" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07b88fb9795d4d36d62a012dfbf49a8f5cf12751f36d31a9dbe66d528e58979e" +dependencies = [ + "socket2", + "winapi 0.3.9", +] + +[[package]] +name = "net2" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" +dependencies = [ + "cfg-if", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "ntapi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a31937dea023539c72ddae0e3571deadc1414b300483fa7aaec176168cfa9d2" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "num-integer" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" +dependencies = [ + "autocfg 1.0.0", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" +dependencies = [ + "autocfg 1.0.0", +] + +[[package]] +name = "numtoa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" + +[[package]] +name = "object" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" + +[[package]] +name = "parking_lot" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" +dependencies = [ + "cfg-if", + "cloudabi", + "libc", + "redox_syscall", + "smallvec", + "winapi 0.3.9", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" + +[[package]] +name = "proc-macro2" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b06e2f335f48d24442b35a19df506a835fb3547bc3c06ef27340da9acf5cae7" +dependencies = [ + "unicode-xid 0.1.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" +dependencies = [ + "unicode-xid 0.2.1", +] + +[[package]] +name = "quote" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9949cfe66888ffe1d53e6ec9d9f3b70714083854be20fd5e271b232a017401e8" +dependencies = [ + "proc-macro2 0.3.8", +] + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2 1.0.19", +] + +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +dependencies = [ + "autocfg 0.1.7", + "libc", + "rand_chacha 0.1.1", + "rand_core 0.4.2", + "rand_hc 0.1.0", + "rand_isaac", + "rand_jitter", + "rand_os", + "rand_pcg", + "rand_xorshift", + "winapi 0.3.9", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc 0.2.0", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +dependencies = [ + "autocfg 0.1.7", + "rand_core 0.3.1", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +dependencies = [ + "libc", + "rand_core 0.4.2", + "winapi 0.3.9", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "winapi 0.3.9", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +dependencies = [ + "autocfg 0.1.7", + "rand_core 0.4.2", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +dependencies = [ + "redox_syscall", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" +dependencies = [ + "proc-macro2 1.0.19", + "quote 1.0.7", + "syn 1.0.35", +] + +[[package]] +name = "serde_json" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3433e879a558dde8b5e8feb2a04899cf34fdde1fafb894687e52105fc1162ac3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" + +[[package]] +name = "signal-hook" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604508c1418b99dfe1925ca9224829bb2a8a9a04dda655cc01fcad46f4ab05ed" +dependencies = [ + "libc", + "mio 0.6.22", + "mio 0.7.0", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" +dependencies = [ + "arc-swap", + "libc", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "smallvec" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3757cb9d89161a2f24e1cf78efa0c1fcff485d18e3f55e0aa3480824ddaa0f3f" + +[[package]] +name = "socket2" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "winapi 0.3.9", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "syn" +version = "0.13.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f9bf6292f3a61d2c716723fdb789a41bbe104168e6f496dc6497e531ea1b9b" +dependencies = [ + "proc-macro2 0.3.8", + "quote 0.5.2", + "unicode-xid 0.1.0", +] + +[[package]] +name = "syn" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb7f4c519df8c117855e19dd8cc851e89eb746fe7a73f0157e0d95fdec5369b0" +dependencies = [ + "proc-macro2 1.0.19", + "quote 1.0.7", + "unicode-xid 0.2.1", +] + +[[package]] +name = "synstructure" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" +dependencies = [ + "proc-macro2 1.0.19", + "quote 1.0.7", + "syn 1.0.35", + "unicode-xid 0.2.1", +] + +[[package]] +name = "task-hookrs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45bfc800c11a6606fd11332688ba3374f5c6381253c9347b6801ff594dd37691" +dependencies = [ + "chrono", + "derive_builder", + "failure", + "log", + "serde", + "serde_derive", + "serde_json", + "uuid", +] + +[[package]] +name = "taskwarrior-tui" +version = "0.1.0" +dependencies = [ + "chrono", + "clap", + "crossterm 0.14.2", + "rand 0.7.3", + "serde", + "serde_json", + "shlex", + "task-hookrs", + "termion", + "tui", + "unicode-width", +] + +[[package]] +name = "termion" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22cec9d8978d906be5ac94bceb5a010d885c626c4c8855721a4dbd20e3ac905" +dependencies = [ + "libc", + "numtoa", + "redox_syscall", + "redox_termios", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "tui" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a977b0bb2e2033a6fef950f218f13622c3c34e59754b704ce3492dedab1dfe95" +dependencies = [ + "bitflags", + "cassowary", + "crossterm 0.17.7", + "termion", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "unicode-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "uuid" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" +dependencies = [ + "rand 0.6.5", + "serde", +] + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[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-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[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 = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2f88d48 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "taskwarrior-tui" +version = "0.1.0" +authors = ["Dheepak Krishnamurthy "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = ["termion-backend"] +termion-backend = ["tui/termion", "termion"] +crossterm-backend = ["tui/crossterm", "crossterm"] + +[dependencies] +clap = "*" +tui = "0.10.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +task-hookrs = "0.7.0" +termion = { version = "1.5.4", optional = true } +crossterm = { version = "0.14.2", optional = true } +rand = "0.7" +shlex = "0.1.1" +chrono = "0.4" +unicode-width = "0.1" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0fc1d9e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Dheepak Krishnamurthy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..9b9e2cc --- /dev/null +++ b/src/app.rs @@ -0,0 +1,383 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Result; +use shlex::split; +use std::cmp::Ordering; +use std::convert::TryInto; +use std::process::Command; + +use task_hookrs::import::import; +use task_hookrs::task::Task; +use task_hookrs::uda::UDAValue; +use task_hookrs::date::Date; +use unicode_width::UnicodeWidthStr; + +use chrono::{Local, DateTime, TimeZone, Duration, NaiveDateTime}; + +use tui::{ + backend::{Backend, TermionBackend}, + layout::{Constraint, Direction, Layout, Rect}, + style::{Color, Modifier, Style}, + terminal::Frame, + text::Text, + widgets::{BarChart, Block, Borders, Row, Paragraph, Table, TableState}, + Terminal, +}; + +pub fn cmp(t1: &Task, t2: &Task) -> Ordering { + let urgency1 = match &t1.uda()["urgency"] { + UDAValue::Str(_) => 0.0, + UDAValue::U64(u) => *u as f64, + UDAValue::F64(f) => *f, + }; + let urgency2 = match &t2.uda()["urgency"] { + UDAValue::Str(_) => 0.0, + UDAValue::U64(u) => *u as f64, + UDAValue::F64(f) => *f, + }; + urgency2.partial_cmp(&urgency1).unwrap() +} + +pub fn vague_format_date_time(dt: &Date) -> String { + let now = Local::now().naive_utc(); + let seconds = (now - NaiveDateTime::new(dt.date(), dt.time())).num_seconds(); + + if seconds >= 60 * 60 * 24 * 365 { + return format!("{}y", seconds / 86400 / 365); + } else if seconds >= 60 * 60 * 24 * 90 { + return format!("{}mo", seconds / 60 / 60 / 24 / 30); + } else if seconds >= 60 * 60 * 24 * 14 { + return format!("{}w", seconds / 60 / 60 / 24 / 7); + } else if seconds >= 60 * 60 * 24 { + return format!("{}d", seconds / 60 / 60 / 24); + } else if seconds >= 60 * 60 { + return format!("{}h", seconds / 60 / 60); + } else if seconds >= 60 { + return format!("{}min", seconds / 60); + } else { + return format!("{}s", seconds); + } +} + +pub enum InputMode { + Normal, + Command, +} + +pub struct App { + pub should_quit: bool, + pub state: TableState, + pub filter: String, + pub tasks: Vec, + pub task_report_labels: Vec, + pub task_report_columns: Vec, + pub input_mode: InputMode, +} + +impl App { + pub fn new() -> App { + let mut app = App { + should_quit: false, + state: TableState::default(), + tasks: vec![], + task_report_labels: vec![], + task_report_columns: vec![], + filter: "status:pending ".to_string(), + input_mode: InputMode::Normal, + }; + app.update(); + app + } + + pub fn draw(&mut self, f: &mut Frame) { + let rects = Layout::default() + .constraints([ + Constraint::Percentage(48), + Constraint::Percentage(48), + Constraint::Max(3), + ].as_ref()) + .split(f.size()); + self.draw_task_report(f, rects[0]); + self.draw_task_details(f, rects[1]); + self.draw_command(f, rects[2]); + match self.input_mode { + InputMode::Normal => (), + InputMode::Command => { + // Make the cursor visible and ask tui-rs to put it at the specified coordinates after rendering + f.set_cursor( + // Put cursor past the end of the input text + rects[2].x + self.filter.width() as u16 + 1, + // Move one line down, from the border to the input line + rects[2].y + 1, + ) + } + } + } + + fn draw_command(&mut self, f: &mut Frame, rect: Rect) { + let p = Paragraph::new(Text::from(&self.filter[..])) + .block(Block::default().borders(Borders::ALL).title("Command")); + f.render_widget(p, rect); + } + + fn draw_task_details(&mut self, f: &mut Frame, rect: Rect) { + if self.tasks.len() == 0 { + f.render_widget(Block::default().borders(Borders::ALL).title("Task not found"), rect); + return (); + } + let selected = self.state.selected().unwrap_or_default(); + let task_id = self.tasks[selected].id().unwrap_or_default(); + let output = Command::new("task") + .arg(format!("{}", task_id)) + .output() + .expect( + &format!("Unable to show details for `task {}`. Check documentation for more information", task_id)[..] + ); + let data = String::from_utf8(output.stdout).unwrap(); + let p = Paragraph::new(Text::from(&data[..])) + .block(Block::default().borders(Borders::ALL).title(format!("Task {}", task_id))); + f.render_widget(p, rect); + } + + fn draw_task_report(&mut self, f: &mut Frame, rect: Rect) { + let active_style = Style::default() + .fg(Color::Blue) + .add_modifier(Modifier::BOLD); + let normal_style = Style::default(); + + let (tasks, headers, widths) = self.task_report(); + if tasks.len() == 0 { + f.render_widget(Block::default().borders(Borders::ALL).title("Task next"), rect); + return (); + } + let header = headers.iter(); + let rows = tasks + .iter() + .map(|i| Row::StyledData(i.iter(), normal_style)); + let constraints: Vec = widths + .iter() + .map(|i| Constraint::Percentage(std::cmp::min(50, std::cmp::max(*i, 5)).try_into().unwrap())) + .collect(); + + let t = Table::new(header, rows) + .block(Block::default().borders(Borders::ALL).title("Task next")) + .highlight_style(normal_style.add_modifier(Modifier::BOLD)) + .highlight_symbol("• ") + .widths(&constraints); + f.render_stateful_widget(t, rect, &mut self.state); + } + + pub fn get_string_attribute(&self, attribute: &str, task: &Task) -> String { + let s = match attribute { + "id" => task.id().unwrap_or_default().to_string(), + // "entry" => task.entry().unwrap().to_string(), + "entry" => vague_format_date_time(task.entry()), + "start" => match task.start() { + Some(v) => vague_format_date_time(v), + None => "".to_string(), + }, + "description" => task.description().to_string(), + "urgency" => match &task.uda()["urgency"] { + UDAValue::Str(s) => "0.0".to_string(), + UDAValue::U64(u) => (*u as f64).to_string(), + UDAValue::F64(f) => (*f).to_string(), + }, + _ => "".to_string(), + }; + return s; + } + + pub fn task_report(&mut self) -> (Vec>, Vec, Vec) { + let mut alltasks = vec![]; + // get all tasks as their string representation + for task in &self.tasks { + let mut item = vec![]; + for name in &self.task_report_columns { + let attribute = name.split(".").collect::>()[0]; + let s = self.get_string_attribute(attribute, task); + item.push(s); + } + alltasks.push(item) + } + + // find which columns are empty + let null_columns_len; + if alltasks.len() > 0 { + null_columns_len = alltasks[0].len(); + } else { + return (vec![], vec![], vec![]); + } + + let mut null_columns = vec![0; null_columns_len]; + for task in &alltasks { + for (i, s) in task.iter().enumerate() { + null_columns[i] += s.len(); + } + } + + // filter out columns where everything is empty + let mut tasks = vec![]; + for task in &alltasks { + let t = task.clone(); + let t: Vec = t + .iter() + .enumerate() + .filter(|&(i, _)| null_columns[i] != 0) + .map(|(_, e)| e.to_owned()) + .collect(); + tasks.push(t); + } + + // filter out header where all columns are empty + let headers: Vec = self + .task_report_labels + .iter() + .enumerate() + .filter(|&(i, _)| null_columns[i] != 0) + .map(|(_, e)| e.to_owned()) + .collect(); + + // set widths proportional to the content + let mut widths: Vec = vec![0; tasks[0].len()]; + for task in &tasks { + for (i, attr) in task.iter().enumerate() { + widths[i] = (attr.len() as i16 * 100 + / task.iter().map(|s| s.len() as i16).sum::()) + .try_into() + .unwrap(); + } + } + + return (tasks, headers, widths); + } + + pub fn update(&mut self) { + self.export_tasks(); + self.export_headers(); + } + pub fn next(&mut self) { + let i = match self.state.selected() { + Some(i) => { + if i >= self.tasks.len() - 1 { + 0 + } else { + i + 1 + } + } + None => 0, + }; + self.state.select(Some(i)); + } + pub fn previous(&mut self) { + let i = match self.state.selected() { + Some(i) => { + if i == 0 { + self.tasks.len() - 1 + } else { + i - 1 + } + } + None => 0, + }; + self.state.select(Some(i)); + } + + pub fn export_headers(&mut self) { + self.task_report_columns = vec![]; + self.task_report_labels = vec![]; + + let output = Command::new("task") + .arg("show") + .arg("report.next.columns") + .output() + .expect("Unable to run `task show report.next.columns`. Check documentation for more information"); + let data = String::from_utf8(output.stdout).unwrap(); + + for line in data.split("\n") { + if line.starts_with("report.next.columns") { + let column_names: &str = line.split(" ").collect::>()[1]; + for column in column_names.split(",") { + self.task_report_columns.push(column.to_string()); + } + } + } + + let output = Command::new("task") + .arg("show") + .arg("report.next.labels") + .output() + .expect("Unable to run `task show report.next.labels`. Check documentation for more information"); + let data = String::from_utf8(output.stdout).unwrap(); + + for line in data.split("\n") { + if line.starts_with("report.next.labels") { + let label_names: &str = line.split(" ").collect::>()[1]; + for label in label_names.split(",") { + self.task_report_labels.push(label.to_string()); + } + } + } + } + + pub fn export_tasks(&mut self) { + let mut task = Command::new("task"); + + task.arg("export"); + + match split(&self.filter) { + Some(cmd) => { + for s in cmd { + task.arg(&s); + } + } + None => { + task.arg(""); + } + } + + let output = task + .output() + .expect("Unable to run `task export`. Check documentation for more information"); + let data = String::from_utf8(output.stdout).unwrap(); + let imported = import(data.as_bytes()); + match imported { + Ok(i) => { + self.tasks = i; + self.tasks.sort_by(cmp); + } + _ => () + } + + } +} + +#[cfg(test)] +mod tests { + use crate::app::App; + use std::io::stdin; + + use task_hookrs::import::import; + use task_hookrs::task::Task; + + #[test] + fn test_app() { + let mut app = App::new(); + app.update(); + + println!("{:?}", app.task_report_columns); + println!("{:?}", app.task_report_labels); + + let (t, h, c) = app.task_report(); + println!("{:?}", t); + println!("{:?}", t); + println!("{:?}", t); + // if let Ok(tasks) = import(stdin()) { + // for task in tasks { + // println!("Task: {}, entered {:?} is {} -> {}", + // task.uuid(), + // task.entry(), + // task.status(), + // task.description()); + // } + // } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..0b97353 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,83 @@ +#![allow(dead_code)] +#![allow(unused_imports)] +#![allow(unused_variables)] + +mod util; + +#[allow(dead_code)] +mod app; + +use crate::util::{Event, Events, Config}; +use std::{error::Error, io}; +use termion::{ + event::Key, + input::MouseTerminal, + raw::{IntoRawMode, RawTerminal}, + screen::AlternateScreen, +}; +use tui::{backend::TermionBackend, Terminal}; +use unicode_width::UnicodeWidthStr; +use std::time::Duration; + +use app::App; +use app::InputMode; + +type B = TermionBackend>>>; + +fn setup_terminal() -> Result, io::Error> { + let stdout = io::stdout().into_raw_mode()?; + let stdout = MouseTerminal::from(stdout); + let stdout = AlternateScreen::from(stdout); + let backend = TermionBackend::new(stdout); + Terminal::new(backend) +} + +fn main() -> Result<(), Box> { + // Terminal initialization + let mut terminal = setup_terminal()?; + + // Setup event handlers + let events = Events::with_config(Config{ + exit_key: Key::Char('q'), + tick_rate: Duration::from_secs(5), + }); + + let mut app = App::new(); + app.next(); + + loop { + terminal.draw(|mut frame| app.draw(&mut frame)).unwrap(); + + // Handle input + if let Event::Input(input) = events.next()? { + match app.input_mode { + InputMode::Normal => match input { + Key::Ctrl('c') | Key::Char('q') => break, + Key::Char('r') => app.update(), + Key::Down | Key::Char('j') => app.next(), + Key::Up | Key::Char('k') => app.previous(), + Key::Char('i') => { + app.input_mode = InputMode::Command; + } + _ => {}, + }, + InputMode::Command => match input { + Key::Char('\n') | Key::Esc => { + app.input_mode = InputMode::Normal; + } + Key::Char(c) => { + app.filter.push(c); + app.update(); + } + Key::Backspace => { + app.filter.pop(); + app.update(); + } + _ => {} + }, + } + } + } + + Ok(()) +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..701c999 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,96 @@ +#[cfg(feature = "termion")] +use std::io; +use std::sync::mpsc; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; +use std::thread; +use std::time::Duration; + +use termion::event::Key; +use termion::input::TermRead; + +pub enum Event { + Input(I), + Tick, +} + +/// A small event handler that wrap termion input and tick events. Each event +/// type is handled in its own thread and returned to a common `Receiver` +pub struct Events { + rx: mpsc::Receiver>, + input_handle: thread::JoinHandle<()>, + ignore_exit_key: Arc, + tick_handle: thread::JoinHandle<()>, +} + +#[derive(Debug, Clone, Copy)] +pub struct Config { + pub exit_key: Key, + pub tick_rate: Duration, +} + +impl Default for Config { + fn default() -> Config { + Config { + exit_key: Key::Char('q'), + tick_rate: Duration::from_millis(250), + } + } +} + +impl Events { + pub fn new() -> Events { + Events::with_config(Config::default()) + } + + pub fn with_config(config: Config) -> Events { + let (tx, rx) = mpsc::channel(); + let ignore_exit_key = Arc::new(AtomicBool::new(false)); + let input_handle = { + let tx = tx.clone(); + let ignore_exit_key = ignore_exit_key.clone(); + thread::spawn(move || { + let stdin = io::stdin(); + for evt in stdin.keys() { + if let Ok(key) = evt { + if let Err(err) = tx.send(Event::Input(key)) { + eprintln!("{}", err); + return; + } + if !ignore_exit_key.load(Ordering::Relaxed) && key == config.exit_key { + return; + } + } + } + }) + }; + let tick_handle = { + thread::spawn(move || loop { + if tx.send(Event::Tick).is_err() { + break; + } + thread::sleep(config.tick_rate); + }) + }; + Events { + rx, + ignore_exit_key, + input_handle, + tick_handle, + } + } + + pub fn next(&self) -> Result, mpsc::RecvError> { + self.rx.recv() + } + + pub fn disable_exit_key(&mut self) { + self.ignore_exit_key.store(true, Ordering::Relaxed); + } + + pub fn enable_exit_key(&mut self) { + self.ignore_exit_key.store(false, Ordering::Relaxed); + } +}