Skip to content

Commit

Permalink
Support importing from replxx history files
Browse files Browse the repository at this point in the history
  • Loading branch information
amosbird committed May 16, 2024
1 parent 3426561 commit e3faa0d
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 3 deletions.
2 changes: 1 addition & 1 deletion crates/atuin-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ atuin-common = { path = "../atuin-common", version = "18.2.0" }

log = { workspace = true }
base64 = { workspace = true }
time = { workspace = true, features = ["macros", "formatting"] }
time = { workspace = true, features = ["macros", "formatting", "parsing"] }
clap = { workspace = true }
eyre = { workspace = true }
directories = { workspace = true }
Expand Down
1 change: 1 addition & 0 deletions crates/atuin-client/src/import/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod bash;
pub mod fish;
pub mod nu;
pub mod nu_histdb;
pub mod replxx;
pub mod resh;
pub mod xonsh;
pub mod xonsh_sqlite;
Expand Down
70 changes: 70 additions & 0 deletions crates/atuin-client/src/import/replxx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use std::{path::PathBuf, str};

use async_trait::async_trait;
use directories::UserDirs;
use eyre::{eyre, Result};
use time::{macros::format_description, OffsetDateTime, PrimitiveDateTime};

use super::{get_histpath, unix_byte_lines, Importer, Loader};
use crate::history::History;
use crate::import::read_to_end;

#[derive(Debug)]
pub struct Replxx {
bytes: Vec<u8>,
}

fn default_histpath() -> Result<PathBuf> {
let user_dirs = UserDirs::new().ok_or_else(|| eyre!("could not find user directories"))?;
let home_dir = user_dirs.home_dir();

// There is no default histfile for replxx.
// For simplicity let's use the most common one.
Ok(home_dir.join(".histfile"))
}

#[async_trait]
impl Importer for Replxx {
const NAME: &'static str = "replxx";

async fn new() -> Result<Self> {
let bytes = read_to_end(get_histpath(default_histpath)?)?;
Ok(Self { bytes })
}

async fn entries(&mut self) -> Result<usize> {
Ok(super::count_lines(&self.bytes) / 2)
}

async fn load(self, h: &mut impl Loader) -> Result<()> {
let mut timestamp = OffsetDateTime::UNIX_EPOCH;

for b in unix_byte_lines(&self.bytes) {
let s = std::str::from_utf8(b)?;
match try_parse_line_as_timestamp(s) {
Some(t) => timestamp = t,
None => {
// replxx uses ETB character (0x17) as line breaker
let cmd = s.replace("\u{0017}", "\n");
let imported = History::import().timestamp(timestamp).command(cmd);

h.push(imported.build().into()).await?;
}
}
}

Ok(())
}
}

fn try_parse_line_as_timestamp(line: &str) -> Option<OffsetDateTime> {
// replxx history date time format: ### yyyy-mm-dd hh:mm:ss.xxx
let date_time_str = line.strip_prefix("### ")?;
let format =
format_description!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:3]");

let primitive_date_time = PrimitiveDateTime::parse(date_time_str, format).ok()?;
// There is no safe way to get local time offset.
// For simplicity let's just assume UTC.
Some(primitive_date_time.assume_utc())
}
10 changes: 8 additions & 2 deletions crates/atuin/src/command/client/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use atuin_client::{
database::Database,
history::History,
import::{
bash::Bash, fish::Fish, nu::Nu, nu_histdb::NuHistDb, resh::Resh, xonsh::Xonsh,
xonsh_sqlite::XonshSqlite, zsh::Zsh, zsh_histdb::ZshHistDb, Importer, Loader,
bash::Bash, fish::Fish, nu::Nu, nu_histdb::NuHistDb, replxx::Replxx, resh::Resh,
xonsh::Xonsh, xonsh_sqlite::XonshSqlite, zsh::Zsh, zsh_histdb::ZshHistDb, Importer, Loader,
},
};

Expand All @@ -26,6 +26,8 @@ pub enum Cmd {
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
Expand Down Expand Up @@ -98,6 +100,9 @@ impl Cmd {
println!("Detected Nushell");
import::<Nu, DB>(db).await
}
} else if shell.ends_with("/replxx") {
println!("Detected Replxx");
import::<Replxx, DB>(db).await
} else {
println!("cannot import {shell} history");
Ok(())
Expand All @@ -107,6 +112,7 @@ impl Cmd {
Self::Zsh => import::<Zsh, DB>(db).await,
Self::ZshHistDb => import::<ZshHistDb, DB>(db).await,
Self::Bash => import::<Bash, DB>(db).await,
Self::Replxx => import::<Replxx, DB>(db).await,
Self::Resh => import::<Resh, DB>(db).await,
Self::Fish => import::<Fish, DB>(db).await,
Self::Nu => import::<Nu, DB>(db).await,
Expand Down

0 comments on commit e3faa0d

Please sign in to comment.