2021-05-09 11:34:15 -06:00
|
|
|
use std::{env, path::PathBuf};
|
2021-02-13 12:37:00 -07:00
|
|
|
|
|
|
|
use eyre::{eyre, Result};
|
|
|
|
use structopt::StructOpt;
|
|
|
|
|
2021-04-26 04:50:31 -06:00
|
|
|
use atuin_client::import::{bash::Bash, zsh::Zsh};
|
2021-05-09 11:34:15 -06:00
|
|
|
use atuin_client::{database::Database, import::Importer};
|
|
|
|
use atuin_client::{history::History, import::resh::Resh};
|
2021-02-13 12:37:00 -07:00
|
|
|
use indicatif::ProgressBar;
|
|
|
|
|
|
|
|
#[derive(StructOpt)]
|
2021-02-14 08:15:26 -07:00
|
|
|
pub enum Cmd {
|
2021-02-13 12:37:00 -07:00
|
|
|
#[structopt(
|
|
|
|
about="import history for the current shell",
|
|
|
|
aliases=&["a", "au", "aut"],
|
|
|
|
)]
|
|
|
|
Auto,
|
|
|
|
|
|
|
|
#[structopt(
|
|
|
|
about="import history from the zsh history file",
|
|
|
|
aliases=&["z", "zs"],
|
|
|
|
)]
|
|
|
|
Zsh,
|
2021-04-26 04:50:31 -06:00
|
|
|
|
|
|
|
#[structopt(
|
|
|
|
about="import history from the bash history file",
|
|
|
|
aliases=&["b", "ba", "bas"],
|
|
|
|
)]
|
|
|
|
Bash,
|
2021-05-08 10:29:46 -06:00
|
|
|
|
|
|
|
#[structopt(
|
|
|
|
about="import history from the resh history file",
|
|
|
|
aliases=&["r", "re", "res"],
|
|
|
|
)]
|
|
|
|
Resh,
|
2021-02-13 12:37:00 -07:00
|
|
|
}
|
|
|
|
|
2021-05-09 11:34:15 -06:00
|
|
|
const BATCH_SIZE: usize = 100;
|
|
|
|
|
2021-02-14 08:15:26 -07:00
|
|
|
impl Cmd {
|
2021-04-25 11:21:52 -06:00
|
|
|
pub async fn run(&self, db: &mut (impl Database + Send + Sync)) -> Result<()> {
|
2021-04-26 04:50:31 -06:00
|
|
|
println!(" Atuin ");
|
2021-02-15 02:07:49 -07:00
|
|
|
println!("======================");
|
|
|
|
println!(" \u{1f30d} ");
|
|
|
|
println!(" \u{1f418}\u{1f418}\u{1f418}\u{1f418} ");
|
|
|
|
println!(" \u{1f422} ");
|
|
|
|
println!("======================");
|
2021-02-14 08:15:26 -07:00
|
|
|
println!("Importing history...");
|
2021-02-13 12:37:00 -07:00
|
|
|
|
2021-02-14 08:15:26 -07:00
|
|
|
match self {
|
|
|
|
Self::Auto => {
|
|
|
|
let shell = env::var("SHELL").unwrap_or_else(|_| String::from("NO_SHELL"));
|
|
|
|
|
2021-02-14 11:40:51 -07:00
|
|
|
if shell.ends_with("/zsh") {
|
2021-02-14 08:15:26 -07:00
|
|
|
println!("Detected ZSH");
|
2021-05-09 11:34:15 -06:00
|
|
|
import::<Zsh<_>, _>(db, BATCH_SIZE).await
|
2021-02-14 08:15:26 -07:00
|
|
|
} else {
|
|
|
|
println!("cannot import {} history", shell);
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-02-13 12:37:00 -07:00
|
|
|
}
|
|
|
|
|
2021-05-09 11:34:15 -06:00
|
|
|
Self::Zsh => import::<Zsh<_>, _>(db, BATCH_SIZE).await,
|
|
|
|
Self::Bash => import::<Bash<_>, _>(db, BATCH_SIZE).await,
|
|
|
|
Self::Resh => import::<Resh, _>(db, BATCH_SIZE).await,
|
2021-05-08 10:29:46 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-09 11:34:15 -06:00
|
|
|
async fn import<I: Importer + Send, DB: Database + Send + Sync>(
|
|
|
|
db: &mut DB,
|
|
|
|
buf_size: usize,
|
|
|
|
) -> Result<()>
|
|
|
|
where
|
|
|
|
I::IntoIter: Send,
|
|
|
|
{
|
|
|
|
println!("Importing history from {}", I::NAME);
|
|
|
|
|
|
|
|
let histpath = get_histpath::<I>()?;
|
|
|
|
let contents = I::parse(histpath)?;
|
|
|
|
|
|
|
|
let iter = contents.into_iter();
|
|
|
|
let progress = if let (_, Some(upper_bound)) = iter.size_hint() {
|
|
|
|
ProgressBar::new(upper_bound as u64)
|
2021-02-14 11:40:51 -07:00
|
|
|
} else {
|
2021-05-09 11:34:15 -06:00
|
|
|
ProgressBar::new_spinner()
|
2021-02-14 11:40:51 -07:00
|
|
|
};
|
2021-02-14 08:15:26 -07:00
|
|
|
|
|
|
|
let mut buf = Vec::<History>::with_capacity(buf_size);
|
2021-05-09 11:34:15 -06:00
|
|
|
let mut iter = progress.wrap_iter(iter);
|
|
|
|
loop {
|
|
|
|
// fill until either no more entries
|
|
|
|
// or until the buffer is full
|
|
|
|
let done = fill_buf(&mut buf, &mut iter);
|
2021-02-14 08:15:26 -07:00
|
|
|
|
2021-05-09 11:34:15 -06:00
|
|
|
// flush
|
|
|
|
db.save_bulk(&buf).await?;
|
2021-02-13 12:37:00 -07:00
|
|
|
|
2021-05-09 11:34:15 -06:00
|
|
|
if done {
|
|
|
|
break;
|
2021-02-14 08:15:26 -07:00
|
|
|
}
|
2021-02-13 12:37:00 -07:00
|
|
|
}
|
|
|
|
|
2021-02-15 13:31:58 -07:00
|
|
|
println!("Import complete!");
|
2021-02-13 12:37:00 -07:00
|
|
|
|
2021-02-14 08:15:26 -07:00
|
|
|
Ok(())
|
2021-02-13 12:37:00 -07:00
|
|
|
}
|
2021-04-26 04:50:31 -06:00
|
|
|
|
2021-05-09 11:34:15 -06:00
|
|
|
fn get_histpath<I: Importer>() -> Result<PathBuf> {
|
|
|
|
if let Ok(p) = env::var("HISTFILE") {
|
|
|
|
is_file(PathBuf::from(p))
|
2021-04-26 04:50:31 -06:00
|
|
|
} else {
|
2021-05-09 11:34:15 -06:00
|
|
|
is_file(I::histpath()?)
|
|
|
|
}
|
|
|
|
}
|
2021-04-26 04:50:31 -06:00
|
|
|
|
2021-05-09 11:34:15 -06:00
|
|
|
fn is_file(p: PathBuf) -> Result<PathBuf> {
|
|
|
|
if p.is_file() {
|
|
|
|
Ok(p)
|
|
|
|
} else {
|
|
|
|
Err(eyre!(
|
|
|
|
"Could not find history file {:?}. Try setting $HISTFILE",
|
|
|
|
p
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
2021-04-26 04:50:31 -06:00
|
|
|
|
2021-05-09 11:34:15 -06:00
|
|
|
fn fill_buf<T, E>(buf: &mut Vec<T>, iter: &mut impl Iterator<Item = Result<T, E>>) -> bool {
|
|
|
|
buf.clear();
|
|
|
|
loop {
|
|
|
|
match iter.next() {
|
|
|
|
Some(Ok(t)) => buf.push(t),
|
|
|
|
Some(Err(_)) => (),
|
|
|
|
None => break true,
|
|
|
|
}
|
2021-04-26 04:50:31 -06:00
|
|
|
|
2021-05-09 11:34:15 -06:00
|
|
|
if buf.len() == buf.capacity() {
|
|
|
|
break false;
|
2021-04-26 04:50:31 -06:00
|
|
|
}
|
|
|
|
}
|
2021-05-09 11:34:15 -06:00
|
|
|
}
|
2021-04-26 04:50:31 -06:00
|
|
|
|
2021-05-09 11:34:15 -06:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::fill_buf;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_fill_buf() {
|
|
|
|
let mut buf = Vec::with_capacity(4);
|
|
|
|
let mut iter = vec![
|
|
|
|
Ok(1),
|
|
|
|
Err(2),
|
|
|
|
Ok(3),
|
|
|
|
Ok(4),
|
|
|
|
Err(5),
|
|
|
|
Ok(6),
|
|
|
|
Ok(7),
|
|
|
|
Err(8),
|
|
|
|
Ok(9),
|
|
|
|
]
|
|
|
|
.into_iter();
|
|
|
|
|
|
|
|
assert!(!fill_buf(&mut buf, &mut iter));
|
|
|
|
assert_eq!(buf, vec![1, 3, 4, 6]);
|
|
|
|
|
|
|
|
assert!(fill_buf(&mut buf, &mut iter));
|
|
|
|
assert_eq!(buf, vec![7, 9]);
|
2021-04-26 04:50:31 -06:00
|
|
|
}
|
|
|
|
}
|