diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ca1561..1ef5889 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,6 +67,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Cache cargo registry + if: runner.os != 'macOS' uses: actions/cache@v1 with: path: ~/.cargo/registry @@ -77,6 +78,7 @@ jobs: path: ~/.cargo/git key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} - name: Cache cargo build + if: runner.os != 'macOS' uses: actions/cache@v1 with: path: target diff --git a/.gitignore b/.gitignore index ea8c4bf..4458c1e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ /target +gobang +gobang.yml diff --git a/Cargo.lock b/Cargo.lock index 8ef4b20..1b39bfd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,1748 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "ahash" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" + +[[package]] +name = "ahash" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "796540673305a66d127804eef19ad696f1f204b8c1025aaca4958c17eab32877" +dependencies = [ + "getrandom 0.2.3", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "atoi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616896e05fc0e2649463a93a15183c6a16bf03413a7af88ef1285ddedfa9cda5" +dependencies = [ + "num-traits", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "bitvec" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "build_const" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" + +[[package]] +name = "bumpalo" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + +[[package]] +name = "cargo-platform" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0226944a63d1bf35a3b5f948dd7c59e263db83695c9e8bffc4037de02e30f1d7" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714a157da7991e23d90686b9524b9e12e0407a108647f52e9328f4b3d51ac7f" +dependencies = [ + "cargo-platform", + "semver", + "semver-parser", + "serde", + "serde_json", +] + +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "cc" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[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.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi 0.3.9", +] + +[[package]] +name = "cpufeatures" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed00c67cb5d0a7d64a44f6ad2668db7e7530311dd53ea79bcd4fb022c64911c8" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" +dependencies = [ + "build_const", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b10ddc024425c88c2ad148c1b0fd53f4c6d38db9697c9f1588381212fa657c9" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +dependencies = [ + "cfg-if 1.0.0", + "lazy_static", +] + +[[package]] +name = "crossterm" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e86d73f2a0b407b5768d10a8c720cf5d2df49a9efc10ca09176d201ead4b7fb" +dependencies = [ + "bitflags", + "crossterm_winapi 0.6.2", + "lazy_static", + "libc", + "mio 0.7.11", + "parking_lot", + "signal-hook", + "winapi 0.3.9", +] + +[[package]] +name = "crossterm" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c36c10130df424b2f3552fcc2ddcd9b28a27b1e54b358b45874f88d1ca6888c" +dependencies = [ + "bitflags", + "crossterm_winapi 0.7.0", + "lazy_static", + "libc", + "mio 0.7.11", + "parking_lot", + "signal-hook", + "winapi 0.3.9", +] + +[[package]] +name = "crossterm_winapi" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2265c3f8e080075d9b6417aa72293fc71662f34b4af2612d8d1b074d29510db" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "crossterm_winapi" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0da8964ace4d3e4a044fd027919b2237000b24315a37c916f61809f1ff2140b9" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[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 = "funty" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" + +[[package]] +name = "futures" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7e43a803dae2fa37c1f6a8fe121e1f7bf9548b4dfc0522a42f34145dadfc27" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1" + +[[package]] +name = "futures-executor" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "badaa6a909fac9e7236d0620a2f57f7664640c56575b71a7552fbd68deafab79" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acc499defb3b348f8d8f3f66415835a9131856ff7714bf10dadfc4ec4bdb29a1" + +[[package]] +name = "futures-macro" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c40298486cdf52cc00cd6d6987892ba502c7656a16a4192a9992b1ccedd121" +dependencies = [ + "autocfg 1.0.1", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a57bead0ceff0d6dde8f465ecd96c9338121bb7717d3e7b108059531870c4282" + +[[package]] +name = "futures-task" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae" + +[[package]] +name = "futures-util" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967" +dependencies = [ + "autocfg 1.0.1", + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite 0.2.6", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.10.2+wasi-snapshot-preview1", +] + [[package]] name = "gobang" version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "crossterm 0.19.0", + "futures", + "serde", + "serde_json", + "sqlx", + "tokio", + "toml", + "tui", + "unicode-width", +] + +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +dependencies = [ + "ahash 0.4.7", +] + +[[package]] +name = "hashlink" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d99cf782f0dc4372d26846bec3de7804ceb5df083c2d4462c0b8d2330e894fa8" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "instant" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +dependencies = [ + "cfg-if 1.0.0", +] + +[[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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] +name = "js-sys" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062" +dependencies = [ + "wasm-bindgen", +] + +[[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" +dependencies = [ + "spin", +] + +[[package]] +name = "lexical-core" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" +dependencies = [ + "arrayvec", + "bitflags", + "cfg-if 1.0.0", + "ryu", + "static_assertions", +] + +[[package]] +name = "libc" +version = "0.2.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" + +[[package]] +name = "libm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" + +[[package]] +name = "lock_api" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + +[[package]] +name = "memchr" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" + +[[package]] +name = "mio" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow 0.2.2", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956" +dependencies = [ + "libc", + "log", + "miow 0.3.7", + "ntapi", + "winapi 0.3.9", +] + +[[package]] +name = "mio-named-pipes" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656" +dependencies = [ + "log", + "mio 0.6.23", + "miow 0.3.7", + "winapi 0.3.9", +] + +[[package]] +name = "mio-uds" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" +dependencies = [ + "iovec", + "libc", + "mio 0.6.23", +] + +[[package]] +name = "miow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "net2" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "nom" +version = "6.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" +dependencies = [ + "bitvec", + "funty", + "lexical-core", + "memchr", + "version_check", +] + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg 1.0.1", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d0a3d5e207573f948a9e5376662aa743a2ea13f7c50a554d7af443a73fbfeba" +dependencies = [ + "autocfg 1.0.1", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d51546d704f52ef14b3c962b5776e53d5b862e5790e40a350d366c209bd7f7a" +dependencies = [ + "autocfg 0.1.7", + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "serde", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg 1.0.1", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg 1.0.1", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg 1.0.1", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi 0.3.9", +] + +[[package]] +name = "pem" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" +dependencies = [ + "base64 0.13.0", + "once_cell", + "regex", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "pin-project-lite" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" + +[[package]] +name = "pin-project-lite" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro-nested" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" + +[[package]] +name = "proc-macro2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[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", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi 0.3.9", +] + +[[package]] +name = "rsa" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3648b669b10afeab18972c105e284a7b953a669b0be3514c27f9b17acab2f9cd" +dependencies = [ + "byteorder", + "digest", + "lazy_static", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pem", + "rand", + "sha2", + "simple_asn1", + "subtle", + "thiserror", + "zeroize", +] + +[[package]] +name = "rustls" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d1126dcf58e93cee7d098dbda643b5f92ed724f1f6a63007c1116eed6700c81" +dependencies = [ + "base64 0.12.3", + "log", + "ring", + "sct", + "webpki", +] + +[[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 = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + +[[package]] +name = "serde" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c4cfa741c5832d0ef7fab46cabed29c2aae926db0b11bb2069edd8db5e64e16" +dependencies = [ + "block-buffer", + "cfg-if 1.0.0", + "cpufeatures", + "digest", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12" +dependencies = [ + "block-buffer", + "cfg-if 1.0.0", + "cpufeatures", + "digest", + "opaque-debug", +] + +[[package]] +name = "signal-hook" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729" +dependencies = [ + "libc", + "mio 0.7.11", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "simple_asn1" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692ca13de57ce0613a363c8c2f1de925adebc81b04c923ac60c5488bb44abe4b" +dependencies = [ + "chrono", + "num-bigint 0.2.6", + "num-traits", +] + +[[package]] +name = "slab" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" + +[[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "sqlformat" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d86e3c77ff882a828346ba401a7ef4b8e440df804491c6064fe8295765de71c" +dependencies = [ + "lazy_static", + "maplit", + "nom", + "regex", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1a98f9bf17b690f026b6fec565293a995b46dfbd6293debcb654dcffd2d1b34" +dependencies = [ + "sqlx-core", + "sqlx-macros", +] + +[[package]] +name = "sqlx-core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36bb6a2ca3345a86493bc3b71eabc2c6c16a8bb1aa476cf5303bee27f67627d7" +dependencies = [ + "ahash 0.6.3", + "atoi", + "base64 0.13.0", + "bitflags", + "byteorder", + "bytes", + "chrono", + "crc", + "crossbeam-channel", + "crossbeam-queue", + "crossbeam-utils", + "digest", + "either", + "futures-channel", + "futures-core", + "futures-util", + "generic-array", + "hashlink", + "hex", + "itoa", + "libc", + "log", + "memchr", + "num-bigint 0.3.2", + "once_cell", + "parking_lot", + "percent-encoding", + "rand", + "rsa", + "rustls", + "sha-1", + "sha2", + "smallvec", + "sqlformat", + "sqlx-rt", + "stringprep", + "thiserror", + "url", + "webpki", + "webpki-roots", + "whoami", +] + +[[package]] +name = "sqlx-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b5ada8b3b565331275ce913368565a273a74faf2a34da58c4dc010ce3286844" +dependencies = [ + "cargo_metadata", + "dotenv", + "either", + "futures", + "heck", + "lazy_static", + "proc-macro2", + "quote", + "sha2", + "sqlx-core", + "sqlx-rt", + "syn", + "url", +] + +[[package]] +name = "sqlx-rt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63fc5454c9dd7aaea3a0eeeb65ca40d06d0d8e7413a8184f7c3a3ffa5056190b" +dependencies = [ + "once_cell", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stringprep" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "subtle" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" + +[[package]] +name = "syn" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "synstructure" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "thiserror" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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 = "tinyvec" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "iovec", + "lazy_static", + "libc", + "memchr", + "mio 0.6.23", + "mio-named-pipes", + "mio-uds", + "num_cpus", + "pin-project-lite 0.1.12", + "signal-hook-registry", + "slab", + "tokio-macros", + "winapi 0.3.9", +] + +[[package]] +name = "tokio-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12831b255bcfa39dc0436b01e19fea231a37db570686c06ee72c423479f889a" +dependencies = [ + "futures-core", + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "toml" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" +dependencies = [ + "serde", +] + +[[package]] +name = "tui" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ced152a8e9295a5b168adc254074525c17ac4a83c90b2716274cc38118bddc9" +dependencies = [ + "bitflags", + "cassowary", + "crossterm 0.18.2", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "typenum" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" + +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + +[[package]] +name = "unicode-bidi" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" + +[[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.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wasm-bindgen" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f" + +[[package]] +name = "web-sys" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" +dependencies = [ + "webpki", +] + +[[package]] +name = "whoami" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4abacf325c958dfeaf1046931d37f2a901b6dfe0968ee965a29e94c6766b2af6" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[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", +] + +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" + +[[package]] +name = "zeroize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c1e130bebaeab2f23886bf9acbaca14b092408c452543c857f66399cd6dab1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] diff --git a/Cargo.toml b/Cargo.toml index c2ab602..41e2383 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,14 @@ authors = ["Takayuki Maeda "] edition = "2018" [dependencies] +tui = { version = "0.14.0", features = ["crossterm"], default-features = false } +crossterm = "0.19" +anyhow = "1.0.38" +unicode-width = "0.1" +sqlx = { version = "0.4.1", features = ["mysql", "chrono", "runtime-tokio-rustls"] } +chrono = "0.4" +tokio = { version = "0.2.22", features = ["full"] } +futures = "0.3.5" +serde_json = "1.0" +serde = "1.0" +toml = "0.4" diff --git a/README.md b/README.md index 5db3c85..8f64334 100644 --- a/README.md +++ b/README.md @@ -6,4 +6,6 @@ A cross-platform terminal database tool written in Rust [![github workflow status](https://img.shields.io/github/workflow/status/TaKO8Ki/gobang/CI/main)](https://github.com/TaKO8Ki/gobang/actions) +![gobang](./resources/gobang.gif) + diff --git a/resources/gobang.gif b/resources/gobang.gif new file mode 100644 index 0000000..ac85192 Binary files /dev/null and b/resources/gobang.gif differ diff --git a/sample.toml b/sample.toml new file mode 100644 index 0000000..2b82b0f --- /dev/null +++ b/sample.toml @@ -0,0 +1,11 @@ +[[conn]] +name = "sample" +user = "root" +host = "localhost" +port = 3306 + +[[conn]] +user = "root" +host = "localhost" +port = 3306 +database = "world" diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..08e90cc --- /dev/null +++ b/src/app.rs @@ -0,0 +1,250 @@ +use crate::{ + user_config::{Connection, UserConfig}, + utils::get_tables, +}; +use sqlx::mysql::MySqlPool; +use tui::widgets::{ListState, TableState}; + +pub enum InputMode { + Normal, + Editing, +} + +pub enum FocusType { + Dabatases(bool), + Tables(bool), + Records(bool), + Connections, +} + +#[derive(Clone)] +pub struct Database { + pub selected_table: ListState, + pub name: String, + pub tables: Vec, +} + +#[derive(Clone)] +pub struct Table { + pub name: String, +} + +pub struct RecordTable { + pub state: TableState, + pub headers: Vec, + pub rows: Vec>, + pub column_index: usize, +} + +impl Default for RecordTable { + fn default() -> Self { + Self { + state: TableState::default(), + headers: vec![], + rows: vec![], + column_index: 0, + } + } +} + +impl RecordTable { + pub fn next(&mut self) { + let i = match self.state.selected() { + Some(i) => { + if i >= self.rows.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.rows.len() - 1 + } else { + i - 1 + } + } + None => 0, + }; + self.state.select(Some(i)); + } + + pub fn next_column(&mut self) { + if self.headers.len() > 9 && self.column_index < self.headers.len() - 9 { + self.column_index += 1 + } + } + + pub fn previous_column(&mut self) { + if self.column_index > 0 { + self.column_index -= 1 + } + } +} + +impl Database { + pub async fn new(name: String, pool: &MySqlPool) -> anyhow::Result { + Ok(Self { + selected_table: ListState::default(), + name: name.clone(), + tables: get_tables(name, pool).await?, + }) + } + + pub fn next(&mut self) { + let i = match self.selected_table.selected() { + Some(i) => { + if i >= self.tables.len() - 1 { + 0 + } else { + i + 1 + } + } + None => 0, + }; + self.selected_table.select(Some(i)); + } + + pub fn previous(&mut self) { + let i = match self.selected_table.selected() { + Some(i) => { + if i == 0 { + self.tables.len() - 1 + } else { + i - 1 + } + } + None => 0, + }; + self.selected_table.select(Some(i)); + } +} + +pub struct App { + pub input: String, + pub input_mode: InputMode, + pub messages: Vec>, + pub selected_database: ListState, + pub databases: Vec, + pub record_table: RecordTable, + pub focus_type: FocusType, + pub user_config: Option, + pub selected_connection: ListState, + pub pool: Option, +} + +impl Default for App { + fn default() -> App { + App { + input: String::new(), + input_mode: InputMode::Normal, + messages: Vec::new(), + selected_database: ListState::default(), + databases: Vec::new(), + record_table: RecordTable::default(), + focus_type: FocusType::Dabatases(false), + user_config: None, + selected_connection: ListState::default(), + pool: None, + } + } +} + +impl App { + pub fn next_database(&mut self) { + let i = match self.selected_database.selected() { + Some(i) => { + if i >= self.databases.len() - 1 { + 0 + } else { + i + 1 + } + } + None => 0, + }; + self.selected_database.select(Some(i)); + } + + pub fn previous_database(&mut self) { + let i = match self.selected_database.selected() { + Some(i) => { + if i == 0 { + self.databases.len() - 1 + } else { + i - 1 + } + } + None => 0, + }; + self.selected_database.select(Some(i)); + } + + pub fn next_connection(&mut self) { + if let Some(config) = &self.user_config { + let i = match self.selected_connection.selected() { + Some(i) => { + if i >= config.conn.len() - 1 { + 0 + } else { + i + 1 + } + } + None => 0, + }; + self.selected_connection.select(Some(i)); + } + } + + pub fn previous_connection(&mut self) { + if let Some(config) = &self.user_config { + let i = match self.selected_connection.selected() { + Some(i) => { + if i == 0 { + config.conn.len() - 1 + } else { + i - 1 + } + } + None => 0, + }; + self.selected_connection.select(Some(i)); + } + } + + pub fn selected_database(&self) -> Option<&Database> { + match self.selected_database.selected() { + Some(i) => match self.databases.get(i) { + Some(db) => Some(db), + None => None, + }, + None => None, + } + } + + pub fn selected_table(&self) -> Option<&Table> { + match self.selected_database() { + Some(db) => match db.selected_table.selected() { + Some(i) => db.tables.get(i), + None => None, + }, + None => None, + } + } + + pub fn selected_connection(&self) -> Option<&Connection> { + match &self.user_config { + Some(config) => match self.selected_connection.selected() { + Some(i) => config.conn.get(i), + None => None, + }, + None => None, + } + } +} diff --git a/src/event/events.rs b/src/event/events.rs new file mode 100644 index 0000000..b004da6 --- /dev/null +++ b/src/event/events.rs @@ -0,0 +1,76 @@ +use crate::event::Key; +use crossterm::event; +use std::{sync::mpsc, thread, time::Duration}; + +#[derive(Debug, Clone, Copy)] +/// Configuration for event handling. +pub struct EventConfig { + /// The key that is used to exit the application. + pub exit_key: Key, + /// The tick rate at which the application will sent an tick event. + pub tick_rate: Duration, +} + +impl Default for EventConfig { + fn default() -> EventConfig { + EventConfig { + exit_key: Key::Ctrl('c'), + tick_rate: Duration::from_millis(250), + } + } +} + +/// An occurred event. +pub enum Event { + /// An input event occurred. + Input(I), + /// An tick event occurred. + Tick, +} + +/// A small event handler that wrap crossterm input and tick event. Each event +/// type is handled in its own thread and returned to a common `Receiver` +pub struct Events { + rx: mpsc::Receiver>, + // Need to be kept around to prevent disposing the sender side. + _tx: mpsc::Sender>, +} + +impl Events { + /// Constructs an new instance of `Events` with the default config. + pub fn new(tick_rate: u64) -> Events { + Events::with_config(EventConfig { + tick_rate: Duration::from_millis(tick_rate), + ..Default::default() + }) + } + + /// Constructs an new instance of `Events` from given config. + pub fn with_config(config: EventConfig) -> Events { + let (tx, rx) = mpsc::channel(); + + let event_tx = tx.clone(); + thread::spawn(move || { + loop { + // poll for tick rate duration, if no event, sent tick event. + if event::poll(config.tick_rate).unwrap() { + if let event::Event::Key(key) = event::read().unwrap() { + let key = Key::from(key); + + event_tx.send(Event::Input(key)).unwrap(); + } + } + + event_tx.send(Event::Tick).unwrap(); + } + }); + + Events { rx, _tx: tx } + } + + /// Attempts to read an event. + /// This function will block the current thread. + pub fn next(&self) -> Result, mpsc::RecvError> { + self.rx.recv() + } +} diff --git a/src/event/key.rs b/src/event/key.rs new file mode 100644 index 0000000..4e29c35 --- /dev/null +++ b/src/event/key.rs @@ -0,0 +1,205 @@ +use crossterm::event; +use std::fmt; + +/// Represents a key. +#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)] +pub enum Key { + /// Both Enter (or Return) and numpad Enter + Enter, + /// Tabulation key + Tab, + /// Backspace key + Backspace, + /// Escape key + Esc, + + /// Left arrow + Left, + /// Right arrow + Right, + /// Up arrow + Up, + /// Down arrow + Down, + + /// Insert key + Ins, + /// Delete key + Delete, + /// Home key + Home, + /// End key + End, + /// Page Up key + PageUp, + /// Page Down key + PageDown, + + /// F0 key + F0, + /// F1 key + F1, + /// F2 key + F2, + /// F3 key + F3, + /// F4 key + F4, + /// F5 key + F5, + /// F6 key + F6, + /// F7 key + F7, + /// F8 key + F8, + /// F9 key + F9, + /// F10 key + F10, + /// F11 key + F11, + /// F12 key + F12, + Char(char), + Ctrl(char), + Alt(char), + Unkown, +} + +impl Key { + /// Returns the function key corresponding to the given number + /// + /// 1 -> F1, etc... + /// + /// # Panics + /// + /// If `n == 0 || n > 12` + pub fn from_f(n: u8) -> Key { + match n { + 0 => Key::F0, + 1 => Key::F1, + 2 => Key::F2, + 3 => Key::F3, + 4 => Key::F4, + 5 => Key::F5, + 6 => Key::F6, + 7 => Key::F7, + 8 => Key::F8, + 9 => Key::F9, + 10 => Key::F10, + 11 => Key::F11, + 12 => Key::F12, + _ => panic!("unknown function key: F{}", n), + } + } +} + +impl fmt::Display for Key { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Key::Alt(' ') => write!(f, ""), + Key::Ctrl(' ') => write!(f, ""), + Key::Char(' ') => write!(f, ""), + Key::Alt(c) => write!(f, "", c), + Key::Ctrl(c) => write!(f, "", c), + Key::Char(c) => write!(f, "{}", c), + Key::Left | Key::Right | Key::Up | Key::Down => write!(f, "<{:?} Arrow Key>", self), + Key::Enter + | Key::Tab + | Key::Backspace + | Key::Esc + | Key::Ins + | Key::Delete + | Key::Home + | Key::End + | Key::PageUp + | Key::PageDown => write!(f, "<{:?}>", self), + _ => write!(f, "{:?}", self), + } + } +} + +impl From for Key { + fn from(key_event: event::KeyEvent) -> Self { + match key_event { + event::KeyEvent { + code: event::KeyCode::Esc, + .. + } => Key::Esc, + event::KeyEvent { + code: event::KeyCode::Backspace, + .. + } => Key::Backspace, + event::KeyEvent { + code: event::KeyCode::Left, + .. + } => Key::Left, + event::KeyEvent { + code: event::KeyCode::Right, + .. + } => Key::Right, + event::KeyEvent { + code: event::KeyCode::Up, + .. + } => Key::Up, + event::KeyEvent { + code: event::KeyCode::Down, + .. + } => Key::Down, + event::KeyEvent { + code: event::KeyCode::Home, + .. + } => Key::Home, + event::KeyEvent { + code: event::KeyCode::End, + .. + } => Key::End, + event::KeyEvent { + code: event::KeyCode::PageUp, + .. + } => Key::PageUp, + event::KeyEvent { + code: event::KeyCode::PageDown, + .. + } => Key::PageDown, + event::KeyEvent { + code: event::KeyCode::Delete, + .. + } => Key::Delete, + event::KeyEvent { + code: event::KeyCode::Insert, + .. + } => Key::Ins, + event::KeyEvent { + code: event::KeyCode::F(n), + .. + } => Key::from_f(n), + event::KeyEvent { + code: event::KeyCode::Enter, + .. + } => Key::Enter, + event::KeyEvent { + code: event::KeyCode::Tab, + .. + } => Key::Tab, + + // First check for char + modifier + event::KeyEvent { + code: event::KeyCode::Char(c), + modifiers: event::KeyModifiers::ALT, + } => Key::Alt(c), + event::KeyEvent { + code: event::KeyCode::Char(c), + modifiers: event::KeyModifiers::CONTROL, + } => Key::Ctrl(c), + + event::KeyEvent { + code: event::KeyCode::Char(c), + .. + } => Key::Char(c), + + _ => Key::Unkown, + } + } +} diff --git a/src/event/mod.rs b/src/event/mod.rs new file mode 100644 index 0000000..e4b704a --- /dev/null +++ b/src/event/mod.rs @@ -0,0 +1,7 @@ +mod events; +mod key; + +pub use self::{ + events::{Event, Events}, + key::Key, +}; diff --git a/src/handlers/create_connection.rs b/src/handlers/create_connection.rs new file mode 100644 index 0000000..1bb72ff --- /dev/null +++ b/src/handlers/create_connection.rs @@ -0,0 +1,13 @@ +use crate::app::{App, FocusType}; +use crate::event::Key; +use sqlx::mysql::MySqlPool; + +pub async fn handler(_key: Key, app: &mut App) -> anyhow::Result<()> { + if let Some(conn) = app.selected_connection() { + app.pool.as_ref().unwrap().close().await; + let pool = MySqlPool::connect(conn.database_url().as_str()).await?; + app.pool = Some(pool); + app.focus_type = FocusType::Dabatases(true); + } + Ok(()) +} diff --git a/src/handlers/database_list.rs b/src/handlers/database_list.rs new file mode 100644 index 0000000..f86d1f0 --- /dev/null +++ b/src/handlers/database_list.rs @@ -0,0 +1,16 @@ +use crate::app::{App, Database}; +use crate::event::Key; +use crate::utils::get_databases; + +pub async fn handler(_key: Key, app: &mut App) -> anyhow::Result<()> { + app.databases = match app.selected_connection() { + Some(conn) => match &conn.database { + Some(database) => { + vec![Database::new(database.clone(), app.pool.as_ref().unwrap()).await?] + } + None => get_databases(app.pool.as_ref().unwrap()).await?, + }, + None => vec![], + }; + Ok(()) +} diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs new file mode 100644 index 0000000..c26115c --- /dev/null +++ b/src/handlers/mod.rs @@ -0,0 +1,94 @@ +pub mod create_connection; +pub mod database_list; +pub mod record_table; + +use crate::app::{App, FocusType, InputMode}; +use crate::event::Key; + +pub async fn handle_app(key: Key, app: &mut App) -> anyhow::Result<()> { + match app.input_mode { + InputMode::Normal => match key { + Key::Char('e') => { + app.input_mode = InputMode::Editing; + } + Key::Char('c') => { + app.focus_type = FocusType::Connections; + } + Key::Char('l') => app.focus_type = FocusType::Records(false), + Key::Char('h') => app.focus_type = FocusType::Tables(false), + Key::Char('j') => { + if let FocusType::Dabatases(_) = app.focus_type { + app.focus_type = FocusType::Tables(false) + } + } + Key::Char('k') => { + if let FocusType::Tables(_) = app.focus_type { + app.focus_type = FocusType::Dabatases(false) + } + } + Key::Right => match app.focus_type { + FocusType::Records(true) => app.record_table.next_column(), + _ => (), + }, + Key::Left => match app.focus_type { + FocusType::Records(true) => app.record_table.previous_column(), + _ => (), + }, + Key::Up => match app.focus_type { + FocusType::Connections => app.previous_connection(), + FocusType::Records(true) => app.record_table.previous(), + FocusType::Dabatases(true) => app.previous_database(), + FocusType::Tables(true) => match app.selected_database.selected() { + Some(index) => { + app.record_table.column_index = 0; + app.databases[index].previous(); + record_table::handler(key, app).await?; + } + None => (), + }, + _ => (), + }, + Key::Down => match app.focus_type { + FocusType::Connections => app.next_connection(), + FocusType::Records(true) => app.record_table.next(), + FocusType::Dabatases(true) => app.next_database(), + FocusType::Tables(true) => match app.selected_database.selected() { + Some(index) => { + app.record_table.column_index = 0; + app.databases[index].next(); + record_table::handler(key, app).await? + } + None => (), + }, + _ => (), + }, + Key::Enter => match app.focus_type { + FocusType::Connections => { + create_connection::handler(key, app).await?; + database_list::handler(key, app).await?; + } + FocusType::Records(false) => app.focus_type = FocusType::Records(true), + FocusType::Dabatases(false) => app.focus_type = FocusType::Dabatases(true), + FocusType::Tables(false) => app.focus_type = FocusType::Tables(true), + _ => (), + }, + _ => {} + }, + InputMode::Editing => match key { + Key::Enter => { + app.messages.push(vec![app.input.drain(..).collect()]); + } + Key::Char(c) => { + app.input.push(c); + } + Key::Backspace => { + app.input.pop(); + } + Key::Esc => { + app.input_mode = InputMode::Normal; + } + _ => {} + }, + } + Ok(()) +} diff --git a/src/handlers/record_table.rs b/src/handlers/record_table.rs new file mode 100644 index 0000000..a2b3532 --- /dev/null +++ b/src/handlers/record_table.rs @@ -0,0 +1,15 @@ +use crate::app::App; +use crate::event::Key; +use crate::utils::get_records; + +pub async fn handler(_key: Key, app: &mut App) -> anyhow::Result<()> { + if let Some(database) = app.selected_database() { + if let Some(table) = app.selected_table() { + let (headers, records) = + get_records(database, table, app.pool.as_ref().unwrap()).await?; + app.record_table.headers = headers; + app.record_table.rows = records; + } + } + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index e7a11a9..066079f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,84 @@ -fn main() { - println!("Hello, world!"); +mod app; +mod event; +mod handlers; +mod ui; +mod user_config; +mod utils; + +use crate::app::FocusType; +use crate::event::{Event, Key}; +use crate::handlers::handle_app; +use crossterm::{ + event::{DisableMouseCapture, EnableMouseCapture}, + execute, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, +}; +use sqlx::mysql::MySqlPool; +use std::io::stdout; +use tui::{backend::CrosstermBackend, Terminal}; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + enable_raw_mode()?; + + let config = user_config::UserConfig::new("sample.toml").unwrap(); + + let mut stdout = stdout(); + execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; + + let backend = CrosstermBackend::new(stdout); + let mut terminal = Terminal::new(backend)?; + let events = event::Events::new(250); + + let mut app = &mut app::App::default(); + app.user_config = Some(config); + let conn = &app.user_config.as_ref().unwrap().conn.get(0).unwrap(); + let pool = MySqlPool::connect( + format!( + "mysql://{user}:@{host}:{port}", + user = conn.user, + host = conn.host, + port = conn.port + ) + .as_str(), + ) + .await?; + app.pool = Some(pool); + + app.databases = utils::get_databases(app.pool.as_ref().unwrap()).await?; + let (headers, records) = utils::get_records( + app.databases.first().unwrap(), + app.databases.first().unwrap().tables.first().unwrap(), + app.pool.as_ref().unwrap(), + ) + .await?; + app.record_table.rows = records; + app.record_table.headers = headers; + app.selected_database.select(Some(0)); + app.focus_type = FocusType::Connections; + + terminal.clear()?; + + loop { + terminal.draw(|f| ui::draw(f, &mut app).unwrap())?; + match events.next()? { + Event::Input(key) => { + if key == Key::Char('q') { + break; + }; + handle_app(key, app).await? + } + Event::Tick => (), + } + } + + disable_raw_mode()?; + execute!( + terminal.backend_mut(), + LeaveAlternateScreen, + DisableMouseCapture + )?; + terminal.show_cursor()?; + + Ok(()) } diff --git a/src/ui/mod.rs b/src/ui/mod.rs new file mode 100644 index 0000000..d24a9ca --- /dev/null +++ b/src/ui/mod.rs @@ -0,0 +1,164 @@ +use crate::app::InputMode; +use crate::app::{App, FocusType}; +use tui::{ + backend::Backend, + layout::{Constraint, Direction, Layout}, + style::{Color, Style}, + text::{Span, Spans}, + widgets::{Block, Borders, Cell, Clear, List, ListItem, Paragraph, Row, Table}, + Frame, +}; +use unicode_width::UnicodeWidthStr; + +pub fn draw(f: &mut Frame<'_, B>, app: &mut App) -> anyhow::Result<()> { + if let FocusType::Connections = app.focus_type { + let percent_x = 60; + let percent_y = 50; + let conns = &app.user_config.as_ref().unwrap().conn; + let connections: Vec = conns + .iter() + .map(|i| { + ListItem::new(vec![Spans::from(Span::raw(i.database_url()))]) + .style(Style::default().fg(Color::White)) + }) + .collect(); + let tasks = List::new(connections) + .block(Block::default().borders(Borders::ALL).title("Connections")) + .highlight_style(Style::default().fg(Color::Green)) + .style(match app.focus_type { + FocusType::Connections => Style::default().fg(Color::Green), + _ => Style::default(), + }); + let popup_layout = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Percentage((100 - percent_y) / 2), + Constraint::Percentage(percent_y), + Constraint::Percentage((100 - percent_y) / 2), + ] + .as_ref(), + ) + .split(f.size()); + + let area = Layout::default() + .direction(Direction::Horizontal) + .constraints( + [ + Constraint::Percentage((100 - percent_x) / 2), + Constraint::Percentage(percent_x), + Constraint::Percentage((100 - percent_x) / 2), + ] + .as_ref(), + ) + .split(popup_layout[1])[1]; + f.render_widget(Clear, area); + f.render_stateful_widget(tasks, area, &mut app.selected_connection); + return Ok(()); + } + + let main_chunks = Layout::default() + .direction(Direction::Vertical) + .margin(2) + .constraints([Constraint::Percentage(15), Constraint::Percentage(85)]) + .direction(Direction::Horizontal) + .split(f.size()); + + let left_chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Percentage(30), Constraint::Percentage(70)].as_ref()) + .split(main_chunks[0]); + let databases: Vec = app + .databases + .iter() + .map(|i| { + ListItem::new(vec![Spans::from(Span::raw(&i.name))]) + .style(Style::default().fg(Color::White)) + }) + .collect(); + let tasks = List::new(databases) + .block(Block::default().borders(Borders::ALL).title("Databases")) + .highlight_style(Style::default().fg(Color::Green)) + .style(match app.focus_type { + FocusType::Dabatases(false) => Style::default().fg(Color::Magenta), + FocusType::Dabatases(true) => Style::default().fg(Color::Green), + _ => Style::default(), + }); + f.render_stateful_widget(tasks, left_chunks[0], &mut app.selected_database); + + let databases = app.databases.clone(); + let tables: Vec = databases[app.selected_database.selected().unwrap_or(0)] + .tables + .iter() + .map(|i| { + ListItem::new(vec![Spans::from(Span::raw(&i.name))]) + .style(Style::default().fg(Color::White)) + }) + .collect(); + let tasks = List::new(tables) + .block(Block::default().borders(Borders::ALL).title("Tables")) + .highlight_style(Style::default().fg(Color::Green)) + .style(match app.focus_type { + FocusType::Tables(false) => Style::default().fg(Color::Magenta), + FocusType::Tables(true) => Style::default().fg(Color::Green), + _ => Style::default(), + }); + f.render_stateful_widget( + tasks, + left_chunks[1], + &mut app.databases[app.selected_database.selected().unwrap_or(0)].selected_table, + ); + + let right_chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Length(3), Constraint::Min(1)].as_ref()) + .split(main_chunks[1]); + + let input = Paragraph::new(app.input.as_ref()) + .style(match app.input_mode { + InputMode::Normal => Style::default(), + InputMode::Editing => Style::default().fg(Color::Yellow), + }) + .block(Block::default().borders(Borders::ALL).title("Query")); + f.render_widget(input, right_chunks[0]); + match app.input_mode { + InputMode::Normal => (), + InputMode::Editing => f.set_cursor( + right_chunks[0].x + app.input.width() as u16 + 1, + right_chunks[0].y + 1, + ), + } + + let header_cells = app.record_table.headers[app.record_table.column_index..] + .iter() + .map(|h| Cell::from(h.to_string()).style(Style::default().fg(Color::White))); + let header = Row::new(header_cells).height(1).bottom_margin(1); + let rows = app.record_table.rows.iter().map(|item| { + let height = item[app.record_table.column_index..] + .iter() + .map(|content| content.chars().filter(|c| *c == '\n').count()) + .max() + .unwrap_or(0) + + 1; + let cells = item[app.record_table.column_index..] + .iter() + .map(|c| Cell::from(c.to_string()).style(Style::default().fg(Color::White))); + Row::new(cells).height(height as u16).bottom_margin(1) + }); + let widths = (0..10) + .map(|_| Constraint::Percentage(10)) + .collect::>(); + let t = Table::new(rows) + .header(header) + .block(Block::default().borders(Borders::ALL).title("Records")) + .highlight_style(Style::default().fg(Color::Green)) + .style(match app.focus_type { + FocusType::Records(false) => Style::default().fg(Color::Magenta), + FocusType::Records(true) => Style::default().fg(Color::Green), + _ => Style::default(), + }) + .widths(&widths); + f.render_stateful_widget(t, right_chunks[1], &mut app.record_table.state); + + Ok(()) +} diff --git a/src/user_config.rs b/src/user_config.rs new file mode 100644 index 0000000..7d1103a --- /dev/null +++ b/src/user_config.rs @@ -0,0 +1,53 @@ +use serde::Deserialize; + +use std::fs::File; +use std::io::{BufReader, Read}; + +#[derive(Debug, Deserialize)] +pub struct UserConfig { + pub conn: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct Connection { + pub name: Option, + pub user: String, + pub host: String, + pub port: u64, + pub database: Option, +} + +impl UserConfig { + pub fn new(path: &str) -> anyhow::Result { + let file = File::open(path)?; + let mut buf_reader = BufReader::new(file); + let mut contents = String::new(); + buf_reader.read_to_string(&mut contents)?; + + let config: Result = toml::from_str(&contents); + match config { + Ok(config) => Ok(config), + Err(e) => panic!("fail to parse config file: {}", e), + } + } +} + +impl Connection { + pub fn database_url(&self) -> String { + match &self.database { + Some(database) => format!( + "mysql://{user}:@{host}:{port}/{database}", + user = self.user, + host = self.host, + port = self.port, + database = database + ), + None => format!( + "mysql://{user}:@{host}:{port}", + user = self.user, + host = self.host, + port = self.port, + ), + } + } +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..6045bf6 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,101 @@ +use crate::app::{Database, Table}; +use chrono::NaiveDate; +use futures::TryStreamExt; +use sqlx::mysql::MySqlPool; +use sqlx::{Column, Executor, Row, TypeInfo}; + +pub async fn get_databases(pool: &MySqlPool) -> anyhow::Result> { + let databases = sqlx::query("show databases") + .fetch_all(pool) + .await? + .iter() + .map(|table| table.get(0)) + .collect::>(); + let mut list = vec![]; + for db in databases { + list.push(Database::new(db, pool).await?) + } + Ok(list) +} + +pub async fn get_tables(database: String, pool: &MySqlPool) -> anyhow::Result> { + let tables = sqlx::query(format!("show tables from `{}`", database).as_str()) + .fetch_all(pool) + .await? + .iter() + .map(|table| Table { name: table.get(0) }) + .collect::>(); + Ok(tables) +} + +pub async fn get_records( + database: &Database, + table: &Table, + pool: &MySqlPool, +) -> anyhow::Result<(Vec, Vec>)> { + pool.execute(format!("use `{}`", database.name).as_str()) + .await?; + let table_name = format!("SELECT * FROM `{}`", table.name); + let mut rows = sqlx::query(table_name.as_str()).fetch(pool); + let headers = sqlx::query(format!("desc `{}`", table.name).as_str()) + .fetch_all(pool) + .await? + .iter() + .map(|table| table.get(0)) + .collect::>(); + let mut records = vec![]; + while let Some(row) = rows.try_next().await? { + let mut row_vec = vec![]; + for col in row.columns() { + let col_name = col.name(); + match col.type_info().clone().name() { + "INT" | "DECIMAL" | "SMALLINT" => match row.try_get(col_name) { + Ok(value) => { + let value: i64 = value; + row_vec.push(value.to_string()) + } + Err(_) => row_vec.push("".to_string()), + }, + "INT UNSIGNED" => match row.try_get(col_name) { + Ok(value) => { + let value: u64 = value; + row_vec.push(value.to_string()) + } + Err(_) => row_vec.push("".to_string()), + }, + "VARCHAR" | "CHAR" => { + let value: String = row.try_get(col_name).unwrap_or("".to_string()); + row_vec.push(value); + } + "DATE" => match row.try_get(col_name) { + Ok(value) => { + let value: NaiveDate = value; + row_vec.push(value.to_string()) + } + Err(_) => row_vec.push("".to_string()), + }, + "TIMESTAMP" => match row.try_get(col_name) { + Ok(value) => { + let value: chrono::DateTime = value; + row_vec.push(value.to_string()) + } + Err(_) => row_vec.push("".to_string()), + }, + "BOOLEAN" => match row.try_get(col_name) { + Ok(value) => { + let value: bool = value; + row_vec.push(value.to_string()) + } + Err(_) => row_vec.push("".to_string()), + }, + "ENUM" => { + let value: String = row.try_get(col_name).unwrap_or("".to_string()); + row_vec.push(value); + } + _ => (), + } + } + records.push(row_vec) + } + Ok((headers, records)) +}