From 7f5310a1aa87cb32499e7f50c864fdaa9a82bd53 Mon Sep 17 00:00:00 2001 From: Conrad Ludgate Date: Mon, 25 Apr 2022 07:13:30 +0100 Subject: [PATCH] history list (#340) --- Cargo.lock | 101 ++---------------- Cargo.toml | 1 - atuin-client/Cargo.toml | 6 +- atuin-client/src/api_client.rs | 2 +- atuin-client/src/history.rs | 4 + atuin-client/src/sync.rs | 9 +- atuin-common/Cargo.toml | 1 - atuin-common/src/utils.rs | 9 -- atuin-server/Cargo.toml | 1 - src/command/client/history.rs | 98 ++++++++++++------ src/command/client/search.rs | 182 ++++++++++++++++----------------- 11 files changed, 175 insertions(+), 239 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 009a922..df2c1bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,7 +93,6 @@ dependencies = [ "pretty_env_logger", "serde", "serde_json", - "tabwriter", "termion", "tokio", "tracing-subscriber", @@ -115,6 +114,7 @@ dependencies = [ "directories", "eyre", "fs-err", + "hex", "itertools", "lazy_static", "log", @@ -123,9 +123,9 @@ dependencies = [ "regex", "reqwest", "rmp-serde", - "rust-crypto", "serde", "serde_json", + "sha2", "shellexpand", "sodiumoxide", "sql-builder", @@ -141,7 +141,6 @@ name = "atuin-common" version = "0.9.1" dependencies = [ "chrono", - "rust-crypto", "serde", "uuid", ] @@ -160,8 +159,7 @@ dependencies = [ "eyre", "fs-err", "http", - "rand 0.8.5", - "rust-crypto", + "rand", "serde", "serde_json", "sodiumoxide", @@ -724,12 +722,6 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bd79fa345a495d3ae89fb7165fec01c0e72f41821d642dda363a1e97975652e" -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - [[package]] name = "futures-channel" version = "0.3.21" @@ -794,12 +786,6 @@ dependencies = [ "slab", ] -[[package]] -name = "gcc" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" - [[package]] name = "generic-array" version = "0.12.4" @@ -1616,29 +1602,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" -dependencies = [ - "libc", - "rand 0.4.6", -] - -[[package]] -name = "rand" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -dependencies = [ - "fuchsia-cprng", - "libc", - "rand_core 0.3.1", - "rdrand", - "winapi", -] - [[package]] name = "rand" version = "0.8.5" @@ -1647,7 +1610,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core 0.6.3", + "rand_core", ] [[package]] @@ -1657,24 +1620,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core", ] -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - [[package]] name = "rand_core" version = "0.6.3" @@ -1684,15 +1632,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "redox_syscall" version = "0.2.13" @@ -1834,19 +1773,6 @@ dependencies = [ "serde", ] -[[package]] -name = "rust-crypto" -version = "0.2.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" -dependencies = [ - "gcc", - "libc", - "rand 0.3.23", - "rustc-serialize", - "time", -] - [[package]] name = "rust-ini" version = "0.18.0" @@ -1857,12 +1783,6 @@ dependencies = [ "ordered-multimap", ] -[[package]] -name = "rustc-serialize" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" - [[package]] name = "rustls" version = "0.19.1" @@ -2172,7 +2092,7 @@ dependencies = [ "once_cell", "paste", "percent-encoding", - "rand 0.8.5", + "rand", "rustls 0.19.1", "serde", "serde_json", @@ -2259,15 +2179,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" -[[package]] -name = "tabwriter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36205cfc997faadcc4b0b87aaef3fbedafe20d38d4959a7ca6ff803564051111" -dependencies = [ - "unicode-width", -] - [[package]] name = "termcolor" version = "1.1.3" diff --git a/Cargo.toml b/Cargo.toml index e6dd5da..2b77a8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,6 @@ chrono-english = "0.1.4" cli-table = "0.4" base64 = "0.13.0" humantime = "2.1.0" -tabwriter = "1.2.1" crossbeam-channel = "0.5.1" clap = { version = "3.1.11", features = ["derive"] } clap_complete = "3.1.2" diff --git a/atuin-client/Cargo.toml b/atuin-client/Cargo.toml index 8b816d0..0191870 100644 --- a/atuin-client/Cargo.toml +++ b/atuin-client/Cargo.toml @@ -16,7 +16,8 @@ sync = [ "urlencoding", "sodiumoxide", "reqwest", - "rust-crypto", + "sha2", + "hex", "rmp-serde", "base64", ] @@ -56,7 +57,8 @@ reqwest = { version = "0.11", features = [ "json", "rustls-tls", ], default-features = false, optional = true } -rust-crypto = { version = "^0.2", optional = true } +hex = { version = "0.4", optional = true } +sha2 = { version = "0.10", optional = true } rmp-serde = { version = "1.0.0", optional = true } base64 = { version = "0.13.0", optional = true } diff --git a/atuin-client/src/api_client.rs b/atuin-client/src/api_client.rs index d907265..528d161 100644 --- a/atuin-client/src/api_client.rs +++ b/atuin-client/src/api_client.rs @@ -10,10 +10,10 @@ use atuin_common::api::{ AddHistoryRequest, CountResponse, LoginRequest, LoginResponse, RegisterResponse, SyncHistoryResponse, }; -use atuin_common::utils::hash_str; use crate::encryption::{decode_key, decrypt}; use crate::history::History; +use crate::sync::hash_str; static APP_USER_AGENT: &str = concat!("atuin/", env!("CARGO_PKG_VERSION"),); diff --git a/atuin-client/src/history.rs b/atuin-client/src/history.rs index 6610b98..c7bf611 100644 --- a/atuin-client/src/history.rs +++ b/atuin-client/src/history.rs @@ -45,4 +45,8 @@ impl History { hostname, } } + + pub fn success(&self) -> bool { + self.exit == 0 || self.duration == -1 + } } diff --git a/atuin-client/src/sync.rs b/atuin-client/src/sync.rs index 9e74961..23f552e 100644 --- a/atuin-client/src/sync.rs +++ b/atuin-client/src/sync.rs @@ -3,13 +3,20 @@ use std::convert::TryInto; use chrono::prelude::*; use eyre::Result; -use atuin_common::{api::AddHistoryRequest, utils::hash_str}; +use atuin_common::api::AddHistoryRequest; use crate::api_client; use crate::database::Database; use crate::encryption::{encrypt, load_encoded_key, load_key}; use crate::settings::{Settings, HISTORY_PAGE_SIZE}; +pub fn hash_str(string: &str) -> String { + use sha2::{Digest, Sha256}; + let mut hasher = Sha256::new(); + hasher.update(string.as_bytes()); + hex::encode(hasher.finalize()) +} + // Currently sync is kinda naive, and basically just pages backwards through // history. This means newly added stuff shows up properly! We also just use // the total count in each database to indicate whether a sync is needed. diff --git a/atuin-common/Cargo.toml b/atuin-common/Cargo.toml index 67e78f3..6640877 100644 --- a/atuin-common/Cargo.toml +++ b/atuin-common/Cargo.toml @@ -11,7 +11,6 @@ repository = "https://github.com/ellie/atuin" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -rust-crypto = "^0.2" chrono = { version = "0.4", features = ["serde"] } serde = { version = "1.0.126", features = ["derive"] } uuid = { version = "1.0", features = ["v4"] } diff --git a/atuin-common/src/utils.rs b/atuin-common/src/utils.rs index 3e3e645..59545a2 100644 --- a/atuin-common/src/utils.rs +++ b/atuin-common/src/utils.rs @@ -3,15 +3,6 @@ use std::path::PathBuf; use chrono::NaiveDate; use uuid::Uuid; -pub fn hash_str(string: &str) -> String { - use crypto::digest::Digest; - use crypto::sha2::Sha256; - let mut hasher = Sha256::new(); - hasher.input_str(string); - - hasher.result_str() -} - pub fn uuid_v4() -> String { Uuid::new_v4().as_simple().to_string() } diff --git a/atuin-server/Cargo.toml b/atuin-server/Cargo.toml index 8e5dbb4..c3e7f66 100644 --- a/atuin-server/Cargo.toml +++ b/atuin-server/Cargo.toml @@ -22,7 +22,6 @@ serde_json = "1.0.75" sodiumoxide = "0.2.6" base64 = "0.13.0" rand = "0.8.4" -rust-crypto = "^0.2" tokio = { version = "1", features = ["full"] } sqlx = { version = "0.5", features = [ "runtime-tokio-rustls", "chrono", "postgres" ] } async-trait = "0.1.49" diff --git a/src/command/client/history.rs b/src/command/client/history.rs index 4f96c44..6e265e3 100644 --- a/src/command/client/history.rs +++ b/src/command/client/history.rs @@ -1,10 +1,9 @@ use std::env; -use std::io::Write; +use std::io::{StdoutLock, Write}; use std::time::Duration; use clap::Subcommand; use eyre::Result; -use tabwriter::TabWriter; use atuin_client::database::{current_context, Database}; use atuin_client::history::History; @@ -53,44 +52,75 @@ pub enum Cmd { }, } -#[allow(clippy::cast_sign_loss)] -pub fn print_list(h: &[History], human: bool, cmd_only: bool) { - let mut writer = TabWriter::new(std::io::stdout()).padding(2); +#[derive(Clone, Copy, Debug)] +pub enum ListMode { + Human, + CmdOnly, + Regular, +} - let lines = h.iter().rev().map(|h| { +impl ListMode { + pub const fn from_flags(human: bool, cmd_only: bool) -> Self { if human { - let duration = humantime::format_duration(Duration::from_nanos(std::cmp::max( - h.duration, 0, - ) as u64)) - .to_string(); - let duration: Vec<&str> = duration.split(' ').collect(); - let duration = duration[0]; - - format!( - "{}\t{}\t{}\n", - h.timestamp.format("%Y-%m-%d %H:%M:%S"), - h.command.trim(), - duration, - ) + ListMode::Human } else if cmd_only { - format!("{}\n", h.command.trim()) + ListMode::CmdOnly } else { - format!( - "{}\t{}\t{}\n", - h.timestamp.timestamp_nanos(), - h.command.trim(), - h.duration - ) + ListMode::Regular } - }); + } +} - for i in lines { - writer - .write_all(i.as_bytes()) - .expect("failed to write to tab writer"); +#[allow(clippy::cast_sign_loss)] +pub fn print_list(h: &[History], list_mode: ListMode) { + let w = std::io::stdout(); + let mut w = w.lock(); + + match list_mode { + ListMode::Human => print_human_list(&mut w, h), + ListMode::CmdOnly => print_cmd_only(&mut w, h), + ListMode::Regular => print_regular(&mut w, h), } - writer.flush().expect("failed to flush tab writer"); + w.flush().expect("failed to flush history"); +} + +#[allow(clippy::cast_sign_loss)] +pub fn print_human_list(w: &mut StdoutLock, h: &[History]) { + for h in h.iter().rev() { + let duration = + humantime::format_duration(Duration::from_nanos(std::cmp::max(h.duration, 0) as u64)) + .to_string(); + let duration: Vec<&str> = duration.split(' ').collect(); + let duration = duration[0]; + + let time = h.timestamp.format("%Y-%m-%d %H:%M:%S"); + let cmd = h.command.trim(); + + writeln!(w, "{time} ยท {duration}\t{cmd}").expect("failed to write history"); + } +} + +#[allow(clippy::cast_sign_loss)] +pub fn print_regular(w: &mut StdoutLock, h: &[History]) { + for h in h.iter().rev() { + let duration = + humantime::format_duration(Duration::from_nanos(std::cmp::max(h.duration, 0) as u64)) + .to_string(); + let duration: Vec<&str> = duration.split(' ').collect(); + let duration = duration[0]; + + let time = h.timestamp.format("%Y-%m-%d %H:%M:%S"); + let cmd = h.command.trim(); + + writeln!(w, "{time}\t{cmd}\t{duration}").expect("failed to write history"); + } +} + +pub fn print_cmd_only(w: &mut StdoutLock, h: &[History]) { + for h in h.iter().rev() { + writeln!(w, "{}", h.command.trim()).expect("failed to write history"); + } } impl Cmd { @@ -195,14 +225,14 @@ impl Cmd { } }; - print_list(&history, *human, *cmd_only); + print_list(&history, ListMode::from_flags(*human, *cmd_only)); Ok(()) } Self::Last { human, cmd_only } => { let last = db.last().await?; - print_list(&[last], *human, *cmd_only); + print_list(&[last], ListMode::from_flags(*human, *cmd_only)); Ok(()) } diff --git a/src/command/client/search.rs b/src/command/client/search.rs index a913e16..45b1f97 100644 --- a/src/command/client/search.rs +++ b/src/command/client/search.rs @@ -23,6 +23,7 @@ use atuin_client::{ }; use super::event::{Event, Events}; +use super::history::ListMode; const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -73,21 +74,33 @@ impl Cmd { db: &mut (impl Database + Send + Sync), settings: &Settings, ) -> Result<()> { - run( - settings, - self.cwd, - self.exit, - self.interactive, - self.human, - self.exclude_exit, - self.exclude_cwd, - self.before, - self.after, - self.cmd_only, - &self.query, - db, - ) - .await + if self.interactive { + let item = select_history( + &self.query, + settings.search_mode, + settings.filter_mode, + settings.style, + db, + ) + .await?; + eprintln!("{}", item); + } else { + let list_mode = ListMode::from_flags(self.human, self.cmd_only); + run_non_interactive( + settings, + list_mode, + self.cwd, + self.exit, + self.exclude_exit, + self.exclude_cwd, + self.before, + self.after, + &self.query, + db, + ) + .await?; + }; + Ok(()) } } @@ -196,7 +209,7 @@ impl State { let duration = Span::styled( duration, - Style::default().fg(if m.exit == 0 || m.duration == -1 { + Style::default().fg(if m.success() { Color::Green } else { Color::Red @@ -565,117 +578,98 @@ async fn select_history( // This is supposed to more-or-less mirror the command line version, so ofc // it is going to have a lot of args #[allow(clippy::too_many_arguments)] -pub async fn run( +async fn run_non_interactive( settings: &Settings, + list_mode: ListMode, cwd: Option, exit: Option, - interactive: bool, - human: bool, exclude_exit: Option, exclude_cwd: Option, before: Option, after: Option, - cmd_only: bool, query: &[String], db: &mut (impl Database + Send + Sync), ) -> 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(); + let dir = if cwd.as_deref() == Some(".") { + 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) - } + Some(current.to_owned()) } else { - None + cwd }; - if interactive { - let item = select_history( - query, + let context = current_context(); + + let results = db + .search( + None, settings.search_mode, settings.filter_mode, - settings.style, - db, + &context, + query.join(" ").as_str(), ) .await?; - eprintln!("{}", item); - } else { - let context = current_context(); - let results = db - .search( - None, - settings.search_mode, - settings.filter_mode, - &context, - query.join(" ").as_str(), - ) - .await?; - - // TODO: This filtering would be better done in the SQL query, I just - // need a nice way of building queries. - let results: Vec = results - .iter() - .filter(|h| { - if let Some(exit) = exit { - if h.exit != exit { - return false; - } + // TODO: This filtering would be better done in the SQL query, I just + // need a nice way of building queries. + let results: Vec = results + .iter() + .filter(|h| { + if let Some(exit) = exit { + if h.exit != exit { + return false; } + } - if let Some(exit) = exclude_exit { - if h.exit == exit { - return false; - } + if let Some(exit) = exclude_exit { + if h.exit == exit { + return false; } + } - if let Some(cwd) = &exclude_cwd { - if h.cwd.as_str() == cwd.as_str() { - return false; - } + if let Some(cwd) = &exclude_cwd { + if h.cwd.as_str() == cwd.as_str() { + return false; } + } - if let Some(cwd) = &dir { - if h.cwd.as_str() != cwd.as_str() { - return false; - } + if let Some(cwd) = &dir { + if h.cwd.as_str() != cwd.as_str() { + return false; } + } - if let Some(before) = &before { - let before = chrono_english::parse_date_string( - before.as_str(), - Utc::now(), - chrono_english::Dialect::Uk, - ); + if let Some(before) = &before { + let before = chrono_english::parse_date_string( + before.as_str(), + Utc::now(), + chrono_english::Dialect::Uk, + ); - if before.is_err() || h.timestamp.gt(&before.unwrap()) { - return false; - } + if before.is_err() || h.timestamp.gt(&before.unwrap()) { + return false; } + } - if let Some(after) = &after { - let after = chrono_english::parse_date_string( - after.as_str(), - Utc::now(), - chrono_english::Dialect::Uk, - ); + if let Some(after) = &after { + let after = chrono_english::parse_date_string( + after.as_str(), + Utc::now(), + chrono_english::Dialect::Uk, + ); - if after.is_err() || h.timestamp.lt(&after.unwrap()) { - return false; - } + if after.is_err() || h.timestamp.lt(&after.unwrap()) { + return false; } + } - true - }) - .map(std::borrow::ToOwned::to_owned) - .collect(); - - super::history::print_list(&results, human, cmd_only); - } + true + }) + .map(std::borrow::ToOwned::to_owned) + .collect(); + super::history::print_list(&results, list_mode); Ok(()) }