Bugfixes, show time ago, perf improvements

Also allow unique listing and more ergonomic cwd usage
This commit is contained in:
Ellie Huxtable 2021-04-21 18:13:51 +01:00
parent a9b117aad7
commit 4a50ce3666
20 changed files with 586 additions and 347 deletions

382
Cargo.lock generated
View file

@ -51,9 +51,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "async-trait"
version = "0.1.49"
version = "0.1.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589652ce7ccb335d1e7ecb3be145425702b290dbcb7029bbeaae263fc1d87b48"
checksum = "0b98e84bbb4cbcdd97da190ba0c58a1bb0de2c1fdf67d159e192ed766aeca722"
dependencies = [
"proc-macro2",
"quote",
@ -94,13 +94,12 @@ dependencies = [
"cli-table",
"directories",
"eyre",
"fern",
"fork",
"humantime",
"humantime 2.1.0",
"indicatif",
"itertools",
"log",
"reqwest",
"pretty_env_logger",
"rusqlite",
"serde 1.0.125",
"serde_derive",
@ -125,7 +124,7 @@ dependencies = [
"directories",
"eyre",
"fern",
"humantime",
"humantime 2.1.0",
"indicatif",
"itertools",
"log",
@ -180,7 +179,6 @@ dependencies = [
"log",
"parse_duration",
"rand 0.8.3",
"reqwest",
"rmp-serde",
"rust-crypto",
"serde 1.0.125",
@ -270,9 +268,9 @@ dependencies = [
[[package]]
name = "build_const"
version = "0.2.1"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39"
checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7"
[[package]]
name = "bumpalo"
@ -282,9 +280,9 @@ checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe"
[[package]]
name = "byteorder"
version = "1.4.2"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
@ -300,9 +298,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]]
name = "cc"
version = "1.0.66"
version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
[[package]]
name = "cfg-if"
@ -326,9 +324,9 @@ dependencies = [
[[package]]
name = "chrono-english"
version = "0.1.4"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4233ee19352739cfdcb5d7c2085005b166f6170ef2845ed9eef27a8fa5f95206"
checksum = "0be5180df5f7c41fc2416bc038bc8d78d44db8136c415b94ccbc95f523dc38e9"
dependencies = [
"chrono",
"scanlex",
@ -402,9 +400,9 @@ dependencies = [
[[package]]
name = "console"
version = "0.14.0"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cc80946b3480f421c2f17ed1cb841753a371c7c5104f51d507e13f532c856aa"
checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45"
dependencies = [
"encode_unicode",
"lazy_static",
@ -421,22 +419,6 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "core-foundation"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
[[package]]
name = "cpuid-bool"
version = "0.1.2"
@ -474,9 +456,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.1"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49"
dependencies = [
"autocfg",
"cfg-if",
@ -601,6 +583,19 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "env_logger"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
dependencies = [
"atty",
"humantime 1.3.0",
"log",
"regex",
"termcolor",
]
[[package]]
name = "eyre"
version = "0.6.5"
@ -639,21 +634,6 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "fork"
version = "0.1.18"
@ -935,9 +915,9 @@ dependencies = [
[[package]]
name = "httparse"
version = "1.3.5"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "615caabe2c3160b313d52ccc905335f4ed5f10881dd63dc5699d47e90be85691"
checksum = "4a1ce40d6fc9764887c2fdc7305c3dcc429ba11ff981c1509416afd5697e4437"
[[package]]
name = "httpdate"
@ -945,6 +925,15 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47"
[[package]]
name = "humantime"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
dependencies = [
"quick-error",
]
[[package]]
name = "humantime"
version = "2.1.0"
@ -976,23 +965,25 @@ dependencies = [
]
[[package]]
name = "hyper-tls"
version = "0.5.0"
name = "hyper-rustls"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64"
dependencies = [
"bytes",
"futures-util",
"hyper",
"native-tls",
"log",
"rustls",
"tokio",
"tokio-native-tls",
"tokio-rustls",
"webpki",
]
[[package]]
name = "idna"
version = "0.2.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89829a5d69c23d348314a7ac337fe39173b61149a9864deabd260983aed48c21"
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
dependencies = [
"matches",
"unicode-bidi",
@ -1001,15 +992,15 @@ dependencies = [
[[package]]
name = "indenter"
version = "0.3.2"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4d5eb2e114fec2b7fe0fadc22888ad2658789bb7acac4dbee9cf8389f971ec8"
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]]
name = "indexmap"
version = "1.6.1"
version = "1.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb1fa934250de4de8aef298d81c729a7d33d8c239daa3a7575e6b92bfc7313b"
checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3"
dependencies = [
"autocfg",
"hashbrown",
@ -1096,9 +1087,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.86"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c"
checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
[[package]]
name = "libsodium-sys"
@ -1130,9 +1121,9 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]]
name = "lock_api"
version = "0.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176"
dependencies = [
"scopeguard",
]
@ -1231,24 +1222,6 @@ dependencies = [
"twoway",
]
[[package]]
name = "native-tls"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4"
dependencies = [
"lazy_static",
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "nom"
version = "5.1.2"
@ -1392,9 +1365,9 @@ checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
[[package]]
name = "once_cell"
version = "1.5.2"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
[[package]]
name = "opaque-debug"
@ -1402,39 +1375,6 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "openssl"
version = "0.10.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a61075b62a23fef5a29815de7536d940aa35ce96d18ce0cc5076272db678a577"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-sys",
]
[[package]]
name = "openssl-probe"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
[[package]]
name = "openssl-sys"
version = "0.9.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "313752393519e876837e09e1fa183ddef0be7735868dced3196f4472d536277f"
dependencies = [
"autocfg",
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "parking_lot"
version = "0.11.1"
@ -1455,7 +1395,7 @@ dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall 0.2.4",
"redox_syscall 0.2.6",
"smallvec",
"winapi",
]
@ -1479,18 +1419,18 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pin-project"
version = "1.0.6"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc174859768806e91ae575187ada95c91a29e96a98dc5d2cd9a1fed039501ba6"
checksum = "c7509cc106041c40a4518d2af7a61530e1eed0e6285296a3d8c5472806ccc4a4"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.0.6"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a490329918e856ed1b083f244e3bfe2d8c4f336407e4ea9e1a9f479ff09049e5"
checksum = "48c950132583b500556b1efd71d45b319029f2b71518d979fcc208e16b42426f"
dependencies = [
"proc-macro2",
"quote",
@ -1521,6 +1461,16 @@ version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "pretty_env_logger"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d"
dependencies = [
"env_logger",
"log",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
@ -1723,9 +1673,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "redox_syscall"
version = "0.2.4"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05ec8ca9416c5ea37062b502703cd7fcb207736bc294f6e0cf367ac6fc234570"
checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041"
dependencies = [
"bitflags",
]
@ -1736,7 +1686,7 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
dependencies = [
"redox_syscall 0.2.4",
"redox_syscall 0.2.6",
]
[[package]]
@ -1757,19 +1707,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
dependencies = [
"getrandom 0.2.2",
"redox_syscall 0.2.4",
"redox_syscall 0.2.6",
]
[[package]]
name = "regex"
version = "1.4.3"
version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
"thread_local",
]
[[package]]
@ -1783,9 +1732,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.6.22"
version = "0.6.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548"
[[package]]
name = "remove_dir_all"
@ -1810,27 +1759,43 @@ dependencies = [
"http",
"http-body",
"hyper",
"hyper-tls",
"hyper-rustls",
"ipnet",
"js-sys",
"lazy_static",
"log",
"mime",
"native-tls",
"percent-encoding",
"pin-project-lite",
"rustls",
"serde 1.0.125",
"serde_json",
"serde_urlencoded",
"tokio",
"tokio-native-tls",
"tokio-rustls",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"webpki-roots",
"winreg",
]
[[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",
]
[[package]]
name = "rmp"
version = "0.8.10"
@ -1904,6 +1869,19 @@ version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
[[package]]
name = "rustls"
version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
dependencies = [
"base64",
"log",
"ring",
"sct",
"webpki",
]
[[package]]
name = "ryu"
version = "1.0.5"
@ -1922,16 +1900,6 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "088c5d71572124929ea7549a8ce98e1a6fd33d0a38367b09027b382e67c033db"
[[package]]
name = "schannel"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
dependencies = [
"lazy_static",
"winapi",
]
[[package]]
name = "scoped-tls"
version = "1.0.0"
@ -1945,26 +1913,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "security-framework"
version = "2.2.0"
name = "sct"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3670b1d2fdf6084d192bc71ead7aabe6c06aa2ea3fbd9cc3ac111fa5c2b1bd84"
checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3676258fd3cfe2c9a0ec99ce3038798d847ce3e4bb17746373eb9f0f1ac16339"
dependencies = [
"core-foundation-sys",
"libc",
"ring",
"untrusted",
]
[[package]]
@ -2074,9 +2029,9 @@ dependencies = [
[[package]]
name = "slab"
version = "0.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
[[package]]
name = "smallvec"
@ -2105,6 +2060,12 @@ dependencies = [
"serde 1.0.125",
]
[[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"
@ -2162,6 +2123,7 @@ dependencies = [
"parking_lot",
"percent-encoding",
"rand 0.8.3",
"rustls",
"serde 1.0.125",
"serde_json",
"sha-1",
@ -2174,6 +2136,8 @@ dependencies = [
"tokio-stream",
"url",
"uuid",
"webpki",
"webpki-roots",
"whoami",
]
@ -2202,10 +2166,9 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6ae97ab05063ed515cdc23d90253213aa24dda0a288c5ec079af3d10f9771bc"
dependencies = [
"native-tls",
"once_cell",
"tokio",
"tokio-native-tls",
"tokio-rustls",
]
[[package]]
@ -2286,7 +2249,7 @@ dependencies = [
"cfg-if",
"libc",
"rand 0.8.3",
"redox_syscall 0.2.4",
"redox_syscall 0.2.6",
"remove_dir_all",
"winapi",
]
@ -2318,7 +2281,7 @@ checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
dependencies = [
"libc",
"numtoa",
"redox_syscall 0.2.4",
"redox_syscall 0.2.6",
"redox_termios",
]
@ -2351,15 +2314,6 @@ dependencies = [
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
dependencies = [
"once_cell",
]
[[package]]
name = "time"
version = "0.1.43"
@ -2372,9 +2326,9 @@ dependencies = [
[[package]]
name = "tinyvec"
version = "1.1.1"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023"
checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342"
dependencies = [
"tinyvec_macros",
]
@ -2387,9 +2341,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.4.0"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "134af885d758d645f0f0505c9a8b3f9bf8a348fd822e112ab5248138348f1722"
checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5"
dependencies = [
"autocfg",
"bytes",
@ -2417,13 +2371,14 @@ dependencies = [
]
[[package]]
name = "tokio-native-tls"
version = "0.3.0"
name = "tokio-rustls"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6"
dependencies = [
"native-tls",
"rustls",
"tokio",
"webpki",
]
[[package]]
@ -2452,9 +2407,9 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.6.5"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5143d049e85af7fbc36f5454d990e62c2df705b3589f123b71f441b6b59f443f"
checksum = "940a12c99365c31ea8dd9ba04ec1be183ffe4920102bb7122c2f515437601e8e"
dependencies = [
"bytes",
"futures-core",
@ -2549,9 +2504,9 @@ dependencies = [
[[package]]
name = "typenum"
version = "1.12.0"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
[[package]]
name = "unicase"
@ -2564,9 +2519,9 @@ dependencies = [
[[package]]
name = "unicode-bidi"
version = "0.3.4"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0"
dependencies = [
"matches",
]
@ -2604,6 +2559,12 @@ 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.1"
@ -2624,9 +2585,9 @@ checksum = "c9232eb53352b4442e40d7900465dfc534e8cb2dc8f18656fcb2ac16112b5593"
[[package]]
name = "utf-8"
version = "0.7.5"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "uuid"
@ -2639,9 +2600,9 @@ dependencies = [
[[package]]
name = "vcpkg"
version = "0.2.11"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb"
checksum = "cbdbff6266a24120518560b5dc983096efb98462e51d0d68169895b237be3e5d"
[[package]]
name = "vec_map"
@ -2651,9 +2612,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.2"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "want"
@ -2784,6 +2745,25 @@ dependencies = [
"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"

View file

@ -15,7 +15,7 @@ atuin-client = { path = "atuin-client", version = "0.6.0" }
atuin-common = { path = "atuin-common", version = "0.6.0" }
log = "0.4"
fern = {version = "0.6.0", features = ["colored"] }
pretty_env_logger = "0.4"
chrono = { version = "0.4", features = ["serde"] }
eyre = "0.6"
structopt = "0.3"
@ -33,7 +33,6 @@ tokio = { version = "1", features = ["full"] }
async-trait = "0.1.49"
chrono-english = "0.1.4"
cli-table = "0.4"
reqwest = { version = "0.11", features = ["blocking", "json"] }
base64 = "0.13.0"
humantime = "2.1.0"

View file

@ -17,6 +17,12 @@ COPY --from=cacher $CARGO_HOME $CARGO_HOME
RUN cargo build --release --bin atuin
FROM debian:buster-slim as runtime
WORKDIR app
ENV TZ=Etc/UTC
ENV RUST_LOG=info
ENV ATUIN_CONFIG_DIR=/config
COPY --from=builder /app/target/release/atuin /usr/local/bin
ENTRYPOINT ["/usr/local/bin/atuin"]

View file

@ -26,7 +26,7 @@ serde = "1.0.125"
serde_json = "1.0.64"
rmp-serde = "0.15.4"
sodiumoxide = "0.2.6"
reqwest = { version = "0.11", features = ["blocking", "json"] }
reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features = false }
base64 = "0.13.0"
parse_duration = "2.1.1"
rand = "0.8.3"

View file

@ -1,15 +1,24 @@
use std::collections::HashMap;
use chrono::Utc;
use eyre::Result;
use reqwest::header::{HeaderMap, AUTHORIZATION};
use reqwest::Url;
use eyre::{eyre, Result};
use reqwest::header::{HeaderMap, AUTHORIZATION, USER_AGENT};
use reqwest::{StatusCode, Url};
use sodiumoxide::crypto::secretbox;
use atuin_common::api::{AddHistoryRequest, CountResponse, SyncHistoryResponse};
use atuin_common::api::{
AddHistoryRequest, CountResponse, LoginResponse, RegisterResponse, SyncHistoryResponse,
};
use atuin_common::utils::hash_str;
use crate::encryption::decrypt;
use crate::encryption::{decode_key, decrypt};
use crate::history::History;
const VERSION: &str = env!("CARGO_PKG_VERSION");
// TODO: remove all references to the encryption key from this
// It should be handled *elsewhere*
pub struct Client<'a> {
sync_addr: &'a str,
token: &'a str,
@ -17,14 +26,70 @@ pub struct Client<'a> {
client: reqwest::Client,
}
pub fn register(
address: &str,
username: &str,
email: &str,
password: &str,
) -> Result<RegisterResponse> {
let mut map = HashMap::new();
map.insert("username", username);
map.insert("email", email);
map.insert("password", password);
let url = format!("{}/user/{}", address, username);
let resp = reqwest::blocking::get(url)?;
if resp.status().is_success() {
return Err(eyre!("username already in use"));
}
let url = format!("{}/register", address);
let client = reqwest::blocking::Client::new();
let resp = client
.post(url)
.header(USER_AGENT, format!("atuin/{}", VERSION))
.json(&map)
.send()?;
if !resp.status().is_success() {
return Err(eyre!("failed to register user"));
}
let session = resp.json::<RegisterResponse>()?;
Ok(session)
}
pub fn login(address: &str, username: &str, password: &str) -> Result<LoginResponse> {
let mut map = HashMap::new();
map.insert("username", username);
map.insert("password", password);
let url = format!("{}/login", address);
let client = reqwest::blocking::Client::new();
let resp = client
.post(url)
.header(USER_AGENT, format!("atuin/{}", VERSION))
.json(&map)
.send()?;
if resp.status() != reqwest::StatusCode::OK {
return Err(eyre!("invalid login details"));
}
let session = resp.json::<LoginResponse>()?;
Ok(session)
}
impl<'a> Client<'a> {
pub fn new(sync_addr: &'a str, token: &'a str, key: secretbox::Key) -> Self {
Client {
pub fn new(sync_addr: &'a str, token: &'a str, key: String) -> Result<Self> {
Ok(Client {
sync_addr,
token,
key,
key: decode_key(key)?,
client: reqwest::Client::new(),
}
})
}
pub async fn count(&self) -> Result<i64> {
@ -36,7 +101,17 @@ impl<'a> Client<'a> {
let mut headers = HeaderMap::new();
headers.insert(AUTHORIZATION, token);
let resp = self.client.get(url).headers(headers).send().await?;
let resp = self
.client
.get(url)
.header(USER_AGENT, format!("atuin/{}", VERSION))
.headers(headers)
.send()
.await?;
if resp.status() != StatusCode::OK {
return Err(eyre!("failed to get count (are you logged in?)"));
}
let count = resp.json::<CountResponse>().await?;
@ -66,6 +141,7 @@ impl<'a> Client<'a> {
.client
.get(url)
.header(AUTHORIZATION, format!("Token {}", self.token))
.header(USER_AGENT, format!("atuin/{}", VERSION))
.send()
.await?;
@ -88,9 +164,33 @@ impl<'a> Client<'a> {
.post(url)
.json(history)
.header(AUTHORIZATION, format!("Token {}", self.token))
.header(USER_AGENT, format!("atuin/{}", VERSION))
.send()
.await?;
Ok(())
}
pub async fn login(&self, username: &str, password: &str) -> Result<LoginResponse> {
let mut map = HashMap::new();
map.insert("username", username);
map.insert("password", password);
let url = format!("{}/login", self.sync_addr);
let resp = self
.client
.post(url)
.json(&map)
.header(USER_AGENT, format!("atuin/{}", VERSION))
.send()
.await?;
if resp.status() != reqwest::StatusCode::OK {
return Err(eyre!("invalid login details"));
}
let session = resp.json::<LoginResponse>().await?;
Ok(session)
}
}

View file

@ -2,7 +2,7 @@ use chrono::prelude::*;
use chrono::Utc;
use std::path::Path;
use eyre::Result;
use eyre::{eyre, Result};
use rusqlite::{params, Connection};
use rusqlite::{Params, Transaction};
@ -14,7 +14,7 @@ pub trait Database {
fn save_bulk(&mut self, h: &[History]) -> Result<()>;
fn load(&self, id: &str) -> Result<History>;
fn list(&self) -> Result<Vec<History>>;
fn list(&self, max: Option<usize>, unique: bool) -> Result<Vec<History>>;
fn range(&self, from: chrono::DateTime<Utc>, to: chrono::DateTime<Utc>)
-> Result<Vec<History>>;
@ -27,6 +27,8 @@ pub trait Database {
fn before(&self, timestamp: chrono::DateTime<Utc>, count: i64) -> Result<Vec<History>>;
fn prefix_search(&self, query: &str) -> Result<Vec<History>>;
fn search(&self, cwd: Option<String>, exit: Option<i64>, query: &str) -> Result<Vec<History>>;
}
// Intended for use on a developer machine and not a sync server.
@ -81,6 +83,16 @@ impl Sqlite {
[],
)?;
conn.execute(
"create index if not exists idx_history_timestamp on history(timestamp)",
[],
)?;
conn.execute(
"create index if not exists idx_history_command on history(command)",
[],
)?;
Ok(())
}
@ -136,16 +148,19 @@ impl Database for Sqlite {
}
fn load(&self, id: &str) -> Result<History> {
debug!("loading history item");
debug!("loading history item {}", id);
let mut stmt = self.conn.prepare(
let history = self.query(
"select id, timestamp, duration, exit, command, cwd, session, hostname from history
where id = ?1",
where id = ?1 limit 1",
&[id],
)?;
let history = stmt.query_row(params![id], |row| {
history_from_sqlite_row(Some(id.to_string()), row)
})?;
if history.is_empty() {
return Err(eyre!("could not find history with id {}", id));
}
let history = history[0].clone();
Ok(history)
}
@ -163,16 +178,39 @@ impl Database for Sqlite {
Ok(())
}
fn list(&self) -> Result<Vec<History>> {
// make a unique list, that only shows the *newest* version of things
fn list(&self, max: Option<usize>, unique: bool) -> Result<Vec<History>> {
debug!("listing history");
let mut stmt = self
.conn
.prepare("SELECT * FROM history order by timestamp asc")?;
// very likely vulnerable to SQL injection
// however, this is client side, and only used by the client, on their
// own data. They can just open the db file...
// otherwise building the query is awkward
let query = format!(
"select * from history h
{}
order by timestamp desc
{}",
// inject the unique check
if unique {
"where timestamp = (
select max(timestamp) from history
where h.command = history.command
)"
} else {
""
},
// inject the limit
if let Some(max) = max {
format!("limit {}", max)
} else {
"".to_string()
}
);
let history_iter = stmt.query_map(params![], |row| history_from_sqlite_row(None, row))?;
let history = self.query(query.as_str(), params![])?;
Ok(history_iter.filter_map(Result::ok).collect())
Ok(history)
}
fn range(
@ -207,7 +245,7 @@ impl Database for Sqlite {
fn last(&self) -> Result<History> {
let mut stmt = self
.conn
.prepare("SELECT * FROM history order by timestamp desc limit 1")?;
.prepare("SELECT * FROM history where duration >= 0 order by timestamp desc limit 1")?;
let history = stmt.query_row(params![], |row| history_from_sqlite_row(None, row))?;
@ -235,9 +273,17 @@ impl Database for Sqlite {
}
fn prefix_search(&self, query: &str) -> Result<Vec<History>> {
let query = query.to_string().replace("*", "%"); // allow wildcard char
self.query(
"select * from history where command like ?1 || '%' order by timestamp asc limit 1000",
&[query],
"select * from history h
where command like ?1 || '%'
and timestamp = (
select max(timestamp) from history
where h.command = history.command
)
order by timestamp desc limit 200",
&[query.as_str()],
)
}
@ -248,6 +294,39 @@ impl Database for Sqlite {
Ok(res)
}
fn search(&self, cwd: Option<String>, exit: Option<i64>, query: &str) -> Result<Vec<History>> {
match (cwd, exit) {
(Some(cwd), Some(exit)) => self.query(
"select * from history
where command like ?1 || '%'
and cwd = ?2
and exit = ?3
order by timestamp asc limit 1000",
&[query, cwd.as_str(), exit.to_string().as_str()],
),
(Some(cwd), None) => self.query(
"select * from history
where command like ?1 || '%'
and cwd = ?2
order by timestamp asc limit 1000",
&[query, cwd.as_str()],
),
(None, Some(exit)) => self.query(
"select * from history
where command like ?1 || '%'
and exit = ?2
order by timestamp asc limit 1000",
&[query, exit.to_string().as_str()],
),
(None, None) => self.query(
"select * from history
where command like ?1 || '%'
order by timestamp asc limit 1000",
&[query],
),
}
}
}
fn history_from_sqlite_row(

View file

@ -29,20 +29,51 @@ pub fn load_key(settings: &Settings) -> Result<secretbox::Key> {
let path = settings.key_path.as_str();
if PathBuf::from(path).exists() {
let bytes = std::fs::read(path)?;
let key: secretbox::Key = rmp_serde::from_read_ref(&bytes)?;
let key = std::fs::read_to_string(path)?;
let key = decode_key(key)?;
Ok(key)
} else {
let key = secretbox::gen_key();
let buf = rmp_serde::to_vec(&key)?;
let encoded = encode_key(key.clone())?;
let mut file = File::create(path)?;
file.write_all(&buf)?;
file.write_all(encoded.as_bytes())?;
Ok(key)
}
}
pub fn load_encoded_key(settings: &Settings) -> Result<String> {
let path = settings.key_path.as_str();
if PathBuf::from(path).exists() {
let key = std::fs::read_to_string(path)?;
Ok(key)
} else {
let key = secretbox::gen_key();
let encoded = encode_key(key)?;
let mut file = File::create(path)?;
file.write_all(encoded.as_bytes())?;
Ok(encoded)
}
}
pub fn encode_key(key: secretbox::Key) -> Result<String> {
let buf = rmp_serde::to_vec(&key)?;
let buf = base64::encode(buf);
Ok(buf)
}
pub fn decode_key(key: String) -> Result<secretbox::Key> {
let buf = base64::decode(key)?;
let buf: secretbox::Key = rmp_serde::from_read_ref(&buf)?;
Ok(buf)
}
pub fn encrypt(history: &History, key: &secretbox::Key) -> Result<EncryptedHistory> {
// serialize with msgpack
let buf = rmp_serde::to_vec(history)?;

View file

@ -6,7 +6,7 @@ use chrono::Utc;
use atuin_common::utils::uuid_v4;
// Any new fields MUST be Optional<>!
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, Ord, PartialOrd)]
pub struct History {
pub id: String,
pub timestamp: chrono::DateTime<Utc>,

View file

@ -78,15 +78,16 @@ impl Settings {
create_dir_all(config_dir)?;
let config_file = if let Ok(p) = std::env::var("ATUIN_CONFIG") {
let mut config_file = if let Ok(p) = std::env::var("ATUIN_CONFIG_DIR") {
PathBuf::from(p)
} else {
let mut config_file = PathBuf::new();
config_file.push(config_dir);
config_file.push("config.toml");
config_file
};
config_file.push("config.toml");
let mut s = Config::new();
let db_path = ProjectDirs::from("com", "elliehuxtable", "atuin")

View file

@ -7,7 +7,7 @@ use atuin_common::{api::AddHistoryRequest, utils::hash_str};
use crate::api_client;
use crate::database::Database;
use crate::encryption::{encrypt, load_key};
use crate::encryption::{encrypt, load_encoded_key, load_key};
use crate::settings::{Settings, HISTORY_PAGE_SIZE};
// Currently sync is kinda naive, and basically just pages backwards through
@ -26,6 +26,8 @@ async fn sync_download(
client: &api_client::Client<'_>,
db: &mut (impl Database + Send),
) -> Result<(i64, i64)> {
debug!("starting sync download");
let remote_count = client.count().await?;
let initial_local = db.history_count()?;
@ -46,14 +48,14 @@ async fn sync_download(
.get_history(last_sync, last_timestamp, host.clone())
.await?;
if page.len() < HISTORY_PAGE_SIZE.try_into().unwrap() {
break;
}
db.save_bulk(&page)?;
local_count = db.history_count()?;
if page.len() < HISTORY_PAGE_SIZE.try_into().unwrap() {
break;
}
let page_last = page
.last()
.expect("could not get last element of page")
@ -80,11 +82,15 @@ async fn sync_upload(
client: &api_client::Client<'_>,
db: &mut (impl Database + Send),
) -> Result<()> {
debug!("starting sync upload");
let initial_remote_count = client.count().await?;
let mut remote_count = initial_remote_count;
let local_count = db.history_count()?;
debug!("remote has {}, we have {}", remote_count, local_count);
let key = load_key(settings)?; // encryption key
// first just try the most recent set
@ -127,8 +133,8 @@ pub async fn sync(settings: &Settings, force: bool, db: &mut (impl Database + Se
let client = api_client::Client::new(
settings.sync_address.as_str(),
settings.session_token.as_str(),
load_key(settings)?,
);
load_encoded_key(settings)?,
)?;
sync_upload(settings, force, &client, db).await?;

View file

@ -25,7 +25,6 @@ serde_json = "1.0.64"
rmp-serde = "0.15.4"
unicode-width = "0.1"
sodiumoxide = "0.2.6"
reqwest = { version = "0.11", features = ["blocking", "json"] }
base64 = "0.13.0"
fork = "0.1.18"
parse_duration = "2.1.1"
@ -33,6 +32,6 @@ rand = "0.8.3"
rust-crypto = "^0.2"
tokio = { version = "1", features = ["full"] }
warp = "0.3"
sqlx = { version = "0.5", features = [ "runtime-tokio-native-tls", "uuid", "chrono", "postgres" ] }
sqlx = { version = "0.5", features = [ "runtime-tokio-rustls", "uuid", "chrono", "postgres" ] }
async-trait = "0.1.49"
urlencoding = "1.1.1"

View file

@ -23,15 +23,16 @@ impl Settings {
create_dir_all(config_dir)?;
let config_file = if let Ok(p) = std::env::var("ATUIN_CONFIG") {
let mut config_file = if let Ok(p) = std::env::var("ATUIN_CONFIG_DIR") {
PathBuf::from(p)
} else {
let mut config_file = PathBuf::new();
config_file.push(config_dir);
config_file.push("server.toml");
config_file
};
config_file.push("server.toml");
// create the config file if it does not exist
let mut s = Config::new();

View file

@ -1,7 +1,6 @@
use std::env;
use eyre::Result;
use fork::{fork, Fork};
use structopt::StructOpt;
use atuin_client::database::Database;
@ -44,6 +43,12 @@ pub enum Cmd {
aliases=&["se", "sea", "sear", "searc"],
)]
Search { query: Vec<String> },
#[structopt(
about="get the last command ran",
aliases=&["la", "las"],
)]
Last {},
}
fn print_list(h: &[History]) {
@ -74,22 +79,24 @@ impl Cmd {
}
let mut h = db.load(id)?;
if h.duration > 0 {
debug!("cannot end history - already has duration");
// returning OK as this can occur if someone Ctrl-c a prompt
return Ok(());
}
h.exit = *exit;
h.duration = chrono::Utc::now().timestamp_nanos() - h.timestamp.timestamp_nanos();
db.update(&h)?;
if settings.should_sync()? {
match fork() {
Ok(Fork::Parent(child)) => {
debug!("launched sync background process with PID {}", child);
}
Ok(Fork::Child) => {
debug!("running periodic background sync");
sync::sync(settings, false, db).await?;
}
Err(_) => println!("Fork failed"),
}
debug!("running periodic background sync");
sync::sync(settings, false, db).await?;
} else {
debug!("sync disabled! not syncing");
}
Ok(())
@ -107,7 +114,7 @@ impl Cmd {
let session = env::var("ATUIN_SESSION")?;
let history = match params {
(false, false) => db.list()?,
(false, false) => db.list(None, false)?,
(true, false) => db.query(QUERY_SESSION, &[session.as_str()])?,
(false, true) => db.query(QUERY_DIR, &[cwd.as_str()])?,
(true, true) => {
@ -126,6 +133,13 @@ impl Cmd {
Ok(())
}
Self::Last {} => {
let last = db.last()?;
print_list(&[last]);
Ok(())
}
}
}
}

View file

@ -1,10 +1,10 @@
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use eyre::{eyre, Result};
use eyre::Result;
use structopt::StructOpt;
use atuin_client::api_client;
use atuin_client::settings::Settings;
#[derive(StructOpt)]
@ -22,25 +22,15 @@ pub struct Cmd {
impl Cmd {
pub fn run(&self, settings: &Settings) -> Result<()> {
let mut map = HashMap::new();
map.insert("username", self.username.clone());
map.insert("password", self.password.clone());
let url = format!("{}/login", settings.sync_address);
let client = reqwest::blocking::Client::new();
let resp = client.post(url).json(&map).send()?;
if resp.status() != reqwest::StatusCode::OK {
return Err(eyre!("invalid login details"));
}
let session = resp.json::<HashMap<String, String>>()?;
let session = session["session"].clone();
let session = api_client::login(
settings.sync_address.as_str(),
self.username.as_str(),
self.password.as_str(),
)?;
let session_path = settings.session_path.as_str();
let mut file = File::create(session_path)?;
file.write_all(session.as_bytes())?;
file.write_all(session.session.as_bytes())?;
let key_path = settings.key_path.as_str();
let mut file = File::create(key_path)?;

View file

@ -43,7 +43,18 @@ pub enum AtuinCmd {
Uuid,
#[structopt(about = "interactive history search")]
Search { query: Vec<String> },
Search {
#[structopt(long, short, about = "filter search result by directory")]
cwd: Option<String>,
#[structopt(long, short, about = "filter search result by exit code")]
exit: Option<i64>,
#[structopt(long, short, about = "open interactive search UI")]
interactive: bool,
query: Vec<String>,
},
#[structopt(about = "sync with the configured server")]
Sync {
@ -76,7 +87,12 @@ impl AtuinCmd {
Self::Server(server) => server.run(&server_settings).await,
Self::Stats(stats) => stats.run(&mut db, &client_settings),
Self::Init => init::init(),
Self::Search { query } => search::run(&query, &mut db),
Self::Search {
cwd,
exit,
interactive,
query,
} => search::run(cwd, exit, interactive, &query, &mut db),
Self::Sync { force } => sync::run(&client_settings, force, &mut db).await,
Self::Login(l) => l.run(&client_settings),

View file

@ -1,10 +1,10 @@
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use eyre::{eyre, Result};
use eyre::Result;
use structopt::StructOpt;
use atuin_client::api_client;
use atuin_client::settings::Settings;
#[derive(StructOpt)]
@ -21,34 +21,11 @@ pub struct Cmd {
}
pub fn run(settings: &Settings, username: &str, email: &str, password: &str) -> Result<()> {
let mut map = HashMap::new();
map.insert("username", username);
map.insert("email", email);
map.insert("password", password);
let url = format!("{}/user/{}", settings.sync_address, username);
let resp = reqwest::blocking::get(url)?;
if resp.status().is_success() {
println!("Username is already in use! Please try another.");
return Ok(());
}
let url = format!("{}/register", settings.sync_address);
let client = reqwest::blocking::Client::new();
let resp = client.post(url).json(&map).send()?;
if !resp.status().is_success() {
println!("Failed to register user - please check your details and try again");
return Err(eyre!("failed to register user"));
}
let session = resp.json::<HashMap<String, String>>()?;
let session = session["session"].clone();
let session = api_client::register(settings.sync_address.as_str(), username, email, password)?;
let path = settings.session_path.as_str();
let mut file = File::create(path)?;
file.write_all(session.as_bytes())?;
file.write_all(session.session.as_bytes())?;
Ok(())
}

View file

@ -1,7 +1,6 @@
use eyre::Result;
use itertools::Itertools;
use std::io::stdout;
use std::time::Duration;
use std::{io::stdout, ops::Sub};
use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen};
use tui::{
@ -31,7 +30,7 @@ struct State {
#[allow(clippy::clippy::cast_sign_loss)]
impl State {
fn durations(&self) -> Vec<String> {
fn durations(&self) -> Vec<(String, String)> {
self.results
.iter()
.map(|h| {
@ -40,7 +39,33 @@ impl State {
let duration = humantime::format_duration(duration).to_string();
let duration: Vec<&str> = duration.split(' ').collect();
duration[0].to_string()
let ago = chrono::Utc::now().sub(h.timestamp);
let ago = humantime::format_duration(ago.to_std().unwrap()).to_string();
let ago: Vec<&str> = ago.split(' ').collect();
(
duration[0]
.to_string()
.replace("days", "d")
.replace("day", "d")
.replace("weeks", "w")
.replace("week", "w")
.replace("months", "mo")
.replace("month", "mo")
.replace("years", "y")
.replace("year", "y"),
ago[0]
.to_string()
.replace("days", "d")
.replace("day", "d")
.replace("weeks", "w")
.replace("week", "w")
.replace("months", "mo")
.replace("month", "mo")
.replace("years", "y")
.replace("year", "y")
+ " ago",
)
})
.collect()
}
@ -51,9 +76,9 @@ impl State {
r: tui::layout::Rect,
) {
let durations = self.durations();
let max_length = durations
.iter()
.fold(0, |largest, i| std::cmp::max(largest, i.len()));
let max_length = durations.iter().fold(0, |largest, i| {
std::cmp::max(largest, i.0.len() + i.1.len())
});
let results: Vec<ListItem> = self
.results
@ -64,10 +89,10 @@ impl State {
let mut command = Span::raw(command);
let mut duration = durations[i].clone();
let (duration, mut ago) = durations[i].clone();
while duration.len() < max_length {
duration.push(' ');
while (duration.len() + ago.len()) < max_length {
ago = " ".to_owned() + ago.as_str();
}
let duration = Span::styled(
@ -79,6 +104,8 @@ impl State {
}),
);
let ago = Span::styled(ago, Style::default().fg(Color::Blue));
if let Some(selected) = self.results_state.selected() {
if selected == i {
command.style =
@ -86,7 +113,8 @@ impl State {
}
}
let spans = Spans::from(vec![duration, Span::raw(" "), command]);
let spans =
Spans::from(vec![duration, Span::raw(" "), ago, Span::raw(" "), command]);
ListItem::new(spans)
})
@ -103,12 +131,12 @@ impl State {
fn query_results(app: &mut State, db: &mut impl Database) {
let results = match app.input.as_str() {
"" => db.list(),
"" => db.list(Some(200), true),
i => db.prefix_search(i),
};
if let Ok(results) = results {
app.results = results.into_iter().rev().unique().collect();
app.results = results;
}
if app.results.is_empty() {
@ -120,7 +148,8 @@ fn query_results(app: &mut State, db: &mut impl Database) {
fn key_handler(input: Key, db: &mut impl Database, app: &mut State) -> Option<String> {
match input {
Key::Esc | Key::Char('\n') => {
Key::Esc => return Some(String::from("")),
Key::Char('\n') => {
let i = app.results_state.selected().unwrap_or(0);
return Some(
@ -268,9 +297,37 @@ fn select_history(query: &[String], db: &mut impl Database) -> Result<String> {
}
}
pub fn run(query: &[String], db: &mut impl Database) -> Result<()> {
let item = select_history(query, db)?;
eprintln!("{}", item);
pub fn run(
cwd: Option<String>,
exit: Option<i64>,
interactive: bool,
query: &[String],
db: &mut impl Database,
) -> Result<()> {
let dir = if let Some(cwd) = cwd {
if cwd == "." {
let current = std::env::current_dir()?;
let current = current.as_os_str();
let current = current.to_str().unwrap();
Some(current.to_owned())
} else {
Some(cwd)
}
} else {
None
};
if interactive {
let item = select_history(query, db)?;
eprintln!("{}", item);
} else {
let results = db.search(dir, exit, query.join(" ").as_str())?;
for i in &results {
println!("{}", i.command);
}
}
Ok(())
}

View file

@ -94,7 +94,7 @@ impl Cmd {
}
Self::All => {
let history = db.list()?;
let history = db.list(None, false)?;
compute_stats(&history)?;

View file

@ -2,7 +2,6 @@
#![allow(clippy::use_self)] // not 100% reliable
use eyre::Result;
use fern::colors::{Color, ColoredLevelConfig};
use structopt::{clap::AppSettings, StructOpt};
#[macro_use]
@ -32,23 +31,7 @@ impl Atuin {
#[tokio::main]
async fn main() -> Result<()> {
let colors = ColoredLevelConfig::new()
.warn(Color::Yellow)
.error(Color::Red);
fern::Dispatch::new()
.format(move |out, message, record| {
out.finish(format_args!(
"{} [{}] {}",
chrono::Local::now().to_rfc3339(),
colors.color(record.level()),
message
))
})
.level(log::LevelFilter::Info)
.level_for("sqlx", log::LevelFilter::Warn)
.chain(std::io::stdout())
.apply()?;
pretty_env_logger::init();
Atuin::from_args().run().await
}

View file

@ -15,8 +15,8 @@ _atuin_precmd(){
[[ -z "${ATUIN_HISTORY_ID}" ]] && return
atuin history end $ATUIN_HISTORY_ID --exit $EXIT
export ATUIN_HISTORY_ID=""
(RUST_LOG=error atuin history end $ATUIN_HISTORY_ID --exit $EXIT &) > /dev/null 2>&1
}
_atuin_search(){
@ -27,7 +27,7 @@ _atuin_search(){
echoti rmkx
# swap stderr and stdout, so that the tui stuff works
# TODO: not this
output=$(atuin search $BUFFER 3>&1 1>&2 2>&3)
output=$(RUST_LOG=error atuin search -i $BUFFER 3>&1 1>&2 2>&3)
echoti smkx
if [[ -n $output ]] ; then