use std::env; use async_trait::async_trait; use clap::Parser; use eyre::Result; use indicatif::ProgressBar; use crate::atuin_client::{ database::Database, history::History, import::{ Importer, Loader, bash::Bash, fish::Fish, nu::Nu, nu_histdb::NuHistDb, powershell::PowerShell, replxx::Replxx, resh::Resh, xonsh::Xonsh, xonsh_sqlite::XonshSqlite, zsh::Zsh, zsh_histdb::ZshHistDb, }, }; #[derive(Parser, Debug)] #[command(infer_subcommands = true)] pub enum Cmd { /// Import history for the current shell Auto, /// Import history from the zsh history file Zsh, /// Import history from the zsh history file ZshHistDb, /// Import history from the bash history file Bash, /// Import history from the replxx history file Replxx, /// Import history from the resh history file Resh, /// Import history from the fish history file Fish, /// Import history from the nu history file Nu, /// Import history from the nu history file NuHistDb, /// Import history from xonsh json files Xonsh, /// Import history from xonsh sqlite db XonshSqlite, /// Import history from the powershell history file Powershell, } const BATCH_SIZE: usize = 100; impl Cmd { #[expect(clippy::cognitive_complexity)] pub async fn run(&self, db: &DB) -> Result<()> { println!(" Atuin "); println!("======================"); println!(" \u{1f30d} "); println!(" \u{1f418}\u{1f418}\u{1f418}\u{1f418} "); println!(" \u{1f422} "); println!("======================"); println!("Importing history..."); match self { Self::Auto => { if cfg!(windows) { return if env::var("PSModulePath").is_ok() { println!("Detected PowerShell"); import::(db).await } else { println!("Could not detect the current shell."); println!("Please run atuin import ."); println!("To view a list of shells, run atuin import."); Ok(()) }; } // $XONSH_HISTORY_BACKEND isn't always set, but $XONSH_HISTORY_FILE is let xonsh_histfile = env::var("XONSH_HISTORY_FILE").unwrap_or_else(|_| String::new()); let shell = env::var("SHELL").unwrap_or_else(|_| String::from("NO_SHELL")); if xonsh_histfile.to_lowercase().ends_with(".json") { println!("Detected Xonsh"); import::(db).await } else if xonsh_histfile.to_lowercase().ends_with(".sqlite") { println!("Detected Xonsh (SQLite backend)"); import::(db).await } else if shell.ends_with("/zsh") { if ZshHistDb::histpath().is_ok() { println!( "Detected Zsh-HistDb, using :{}", ZshHistDb::histpath().unwrap().to_str().unwrap() ); import::(db).await } else { println!("Detected ZSH"); import::(db).await } } else if shell.ends_with("/fish") { println!("Detected Fish"); import::(db).await } else if shell.ends_with("/bash") { println!("Detected Bash"); import::(db).await } else if shell.ends_with("/nu") { if NuHistDb::histpath().is_ok() { println!( "Detected Nu-HistDb, using :{}", NuHistDb::histpath().unwrap().to_str().unwrap() ); import::(db).await } else { println!("Detected Nushell"); import::(db).await } } else if shell.ends_with("/pwsh") { println!("Detected PowerShell"); import::(db).await } else { println!("cannot import {shell} history"); Ok(()) } } Self::Zsh => import::(db).await, Self::ZshHistDb => import::(db).await, Self::Bash => import::(db).await, Self::Replxx => import::(db).await, Self::Resh => import::(db).await, Self::Fish => import::(db).await, Self::Nu => import::(db).await, Self::NuHistDb => import::(db).await, Self::Xonsh => import::(db).await, Self::XonshSqlite => import::(db).await, Self::Powershell => import::(db).await, } } } pub struct HistoryImporter<'db, DB: Database> { pb: ProgressBar, buf: Vec, db: &'db DB, } impl<'db, DB: Database> HistoryImporter<'db, DB> { fn new(db: &'db DB, len: usize) -> Self { Self { pb: ProgressBar::new(len as u64), buf: Vec::with_capacity(BATCH_SIZE), db, } } async fn flush(self) -> Result<()> { if !self.buf.is_empty() { self.db.save_bulk(&self.buf).await?; } self.pb.finish(); Ok(()) } } #[async_trait] impl Loader for HistoryImporter<'_, DB> { async fn push(&mut self, hist: History) -> Result<()> { self.pb.inc(1); self.buf.push(hist); if self.buf.len() == self.buf.capacity() { self.db.save_bulk(&self.buf).await?; self.buf.clear(); } Ok(()) } } async fn import(db: &DB) -> Result<()> { println!("Importing history from {}", I::NAME); let mut importer = I::new().await?; let len = importer.entries().await.unwrap(); let mut loader = HistoryImporter::new(db, len); importer.load(&mut loader).await?; loader.flush().await?; println!("Import complete!"); Ok(()) }