feat: Add xonsh history import (#1678)

* add importers for xonsh JSON files and SQLite db

* rustfmt xonsh importers

* remove env-dependent tests from xonsh importers

* pass xonsh_data_dir into path resolver instead of looking up in env

* review: run format

* review: fix clippy errors

---------

Co-authored-by: Ellie Huxtable <ellie@elliehuxtable.com>
This commit is contained in:
jfmontanaro 2024-02-12 02:32:07 -08:00 committed by GitHub
parent 8ef5f67f8b
commit 87e19df9c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 494 additions and 2 deletions

View File

@ -13,6 +13,8 @@ pub mod fish;
pub mod nu;
pub mod nu_histdb;
pub mod resh;
pub mod xonsh;
pub mod xonsh_sqlite;
pub mod zsh;
pub mod zsh_histdb;

View File

@ -0,0 +1,238 @@
use std::env;
use std::fs::{self, File};
use std::path::{Path, PathBuf};
use async_trait::async_trait;
use directories::BaseDirs;
use eyre::{eyre, Result};
use serde::Deserialize;
use time::OffsetDateTime;
use uuid::timestamp::{context::NoContext, Timestamp};
use uuid::Uuid;
use super::{Importer, Loader};
use crate::history::History;
// Note: both HistoryFile and HistoryData have other keys present in the JSON, we don't
// care about them so we leave them unspecified so as to avoid deserializing unnecessarily.
#[derive(Debug, Deserialize)]
struct HistoryFile {
data: HistoryData,
}
#[derive(Debug, Deserialize)]
struct HistoryData {
sessionid: String,
cmds: Vec<HistoryCmd>,
}
#[derive(Debug, Deserialize)]
struct HistoryCmd {
cwd: String,
inp: String,
rtn: Option<i64>,
ts: (f64, f64),
}
#[derive(Debug)]
pub struct Xonsh {
// history is stored as a bunch of json files, one per session
sessions: Vec<HistoryData>,
hostname: String,
}
fn get_hist_dir(xonsh_data_dir: Option<String>) -> Result<PathBuf> {
// if running within xonsh, this will be available
if let Some(d) = xonsh_data_dir {
let mut path = PathBuf::from(d);
path.push("history_json");
return Ok(path);
}
// otherwise, fall back to default
let base = BaseDirs::new().ok_or_else(|| eyre!("Could not determine home directory"))?;
let hist_dir = base.data_dir().join("xonsh/history_json");
if hist_dir.exists() || cfg!(test) {
Ok(hist_dir)
} else {
Err(eyre!("Could not find xonsh history files"))
}
}
fn get_hostname() -> String {
format!(
"{}:{}",
env::var("ATUIN_HOST_NAME").unwrap_or_else(|_| whoami::hostname()),
env::var("ATUIN_HOST_USER").unwrap_or_else(|_| whoami::username()),
)
}
fn load_sessions(hist_dir: &Path) -> Result<Vec<HistoryData>> {
let mut sessions = vec![];
for entry in fs::read_dir(hist_dir)? {
let p = entry?.path();
let ext = p.extension().and_then(|e| e.to_str());
if p.is_file() && ext == Some("json") {
if let Some(data) = load_session(&p)? {
sessions.push(data);
}
}
}
Ok(sessions)
}
fn load_session(path: &Path) -> Result<Option<HistoryData>> {
let file = File::open(path)?;
// empty files are not valid json, so we can't deserialize them
if file.metadata()?.len() == 0 {
return Ok(None);
}
let mut hist_file: HistoryFile = serde_json::from_reader(file)?;
// if there are commands in this session, replace the existing UUIDv4
// with a UUIDv7 generated from the timestamp of the first command
if let Some(cmd) = hist_file.data.cmds.first() {
let seconds = cmd.ts.0.trunc() as u64;
let nanos = (cmd.ts.0.fract() * 1_000_000_000_f64) as u32;
let ts = Timestamp::from_unix(NoContext, seconds, nanos);
hist_file.data.sessionid = Uuid::new_v7(ts).to_string();
}
Ok(Some(hist_file.data))
}
#[async_trait]
impl Importer for Xonsh {
const NAME: &'static str = "xonsh";
async fn new() -> Result<Self> {
let hist_dir = get_hist_dir(env::var("XONSH_DATA_DIR").ok())?;
let sessions = load_sessions(&hist_dir)?;
let hostname = get_hostname();
Ok(Xonsh { sessions, hostname })
}
async fn entries(&mut self) -> Result<usize> {
let total = self.sessions.iter().map(|s| s.cmds.len()).sum();
Ok(total)
}
async fn load(self, loader: &mut impl Loader) -> Result<()> {
for session in self.sessions {
for cmd in session.cmds {
let (start, end) = cmd.ts;
let ts_nanos = (start * 1_000_000_000_f64) as i128;
let timestamp = OffsetDateTime::from_unix_timestamp_nanos(ts_nanos)?;
let duration = (end - start) * 1_000_000_000_f64;
match cmd.rtn {
Some(exit) => {
let entry = History::import()
.timestamp(timestamp)
.duration(duration.trunc() as i64)
.exit(exit)
.command(cmd.inp.trim())
.cwd(cmd.cwd)
.session(session.sessionid.clone())
.hostname(self.hostname.clone());
loader.push(entry.build().into()).await?;
}
None => {
let entry = History::import()
.timestamp(timestamp)
.duration(duration.trunc() as i64)
.command(cmd.inp.trim())
.cwd(cmd.cwd)
.session(session.sessionid.clone())
.hostname(self.hostname.clone());
loader.push(entry.build().into()).await?;
}
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use time::macros::datetime;
use super::*;
use crate::history::History;
use crate::import::tests::TestLoader;
#[test]
fn test_hist_dir_xonsh() {
let hist_dir = get_hist_dir(Some("/home/user/xonsh_data".to_string())).unwrap();
assert_eq!(
hist_dir,
PathBuf::from("/home/user/xonsh_data/history_json")
);
}
#[tokio::test]
async fn test_import() {
let dir = PathBuf::from("tests/data/xonsh");
let sessions = load_sessions(&dir).unwrap();
let hostname = "box:user".to_string();
let xonsh = Xonsh { sessions, hostname };
let mut loader = TestLoader::default();
xonsh.load(&mut loader).await.unwrap();
// order in buf will depend on filenames, so sort by timestamp for consistency
loader.buf.sort_by_key(|h| h.timestamp);
for (actual, expected) in loader.buf.iter().zip(expected_hist_entries().iter()) {
assert_eq!(actual.timestamp, expected.timestamp);
assert_eq!(actual.command, expected.command);
assert_eq!(actual.cwd, expected.cwd);
assert_eq!(actual.exit, expected.exit);
assert_eq!(actual.duration, expected.duration);
assert_eq!(actual.hostname, expected.hostname);
}
}
fn expected_hist_entries() -> [History; 4] {
[
History::import()
.timestamp(datetime!(2024-02-6 04:17:59.478272256 +00:00:00))
.command("echo hello world!".to_string())
.cwd("/home/user/Documents/code/atuin".to_string())
.exit(0)
.duration(4651069)
.hostname("box:user".to_string())
.build()
.into(),
History::import()
.timestamp(datetime!(2024-02-06 04:18:01.70632832 +00:00:00))
.command("ls -l".to_string())
.cwd("/home/user/Documents/code/atuin".to_string())
.exit(0)
.duration(21288633)
.hostname("box:user".to_string())
.build()
.into(),
History::import()
.timestamp(datetime!(2024-02-06 17:41:31.142515968 +00:00:00))
.command("false".to_string())
.cwd("/home/user/Documents/code/atuin/atuin-client".to_string())
.exit(1)
.duration(10269403)
.hostname("box:user".to_string())
.build()
.into(),
History::import()
.timestamp(datetime!(2024-02-06 17:41:32.271584 +00:00:00))
.command("exit".to_string())
.cwd("/home/user/Documents/code/atuin/atuin-client".to_string())
.exit(0)
.duration(4259347)
.hostname("box:user".to_string())
.build()
.into(),
]
}
}

View File

@ -0,0 +1,222 @@
use std::env;
use std::path::PathBuf;
use async_trait::async_trait;
use directories::BaseDirs;
use eyre::{eyre, Result};
use futures::TryStreamExt;
use sqlx::{sqlite::SqlitePool, FromRow, Row};
use time::OffsetDateTime;
use uuid::timestamp::{context::NoContext, Timestamp};
use uuid::Uuid;
use super::{Importer, Loader};
use crate::history::History;
#[derive(Debug, FromRow)]
struct HistDbEntry {
inp: String,
rtn: Option<i64>,
tsb: f64,
tse: f64,
cwd: String,
session_start: f64,
}
impl HistDbEntry {
fn into_hist_with_hostname(self, hostname: String) -> History {
let ts_nanos = (self.tsb * 1_000_000_000_f64) as i128;
let timestamp = OffsetDateTime::from_unix_timestamp_nanos(ts_nanos).unwrap();
let session_ts_seconds = self.session_start.trunc() as u64;
let session_ts_nanos = (self.session_start.fract() * 1_000_000_000_f64) as u32;
let session_ts = Timestamp::from_unix(NoContext, session_ts_seconds, session_ts_nanos);
let session_id = Uuid::new_v7(session_ts).to_string();
let duration = (self.tse - self.tsb) * 1_000_000_000_f64;
if let Some(exit) = self.rtn {
let imported = History::import()
.timestamp(timestamp)
.duration(duration.trunc() as i64)
.exit(exit)
.command(self.inp)
.cwd(self.cwd)
.session(session_id)
.hostname(hostname);
imported.build().into()
} else {
let imported = History::import()
.timestamp(timestamp)
.duration(duration.trunc() as i64)
.command(self.inp)
.cwd(self.cwd)
.session(session_id)
.hostname(hostname);
imported.build().into()
}
}
}
fn get_db_path(xonsh_data_dir: Option<String>) -> Result<PathBuf> {
// if running within xonsh, this will be available
if let Some(d) = xonsh_data_dir {
let mut path = PathBuf::from(d);
path.push("xonsh-history.sqlite");
return Ok(path);
}
// otherwise, fall back to default
let base = BaseDirs::new().ok_or_else(|| eyre!("Could not determine home directory"))?;
let hist_file = base.data_dir().join("xonsh/xonsh-history.sqlite");
if hist_file.exists() || cfg!(test) {
Ok(hist_file)
} else {
Err(eyre!(
"Could not find xonsh history db at: {}",
hist_file.to_string_lossy()
))
}
}
fn get_hostname() -> String {
format!(
"{}:{}",
env::var("ATUIN_HOST_NAME").unwrap_or_else(|_| whoami::hostname()),
env::var("ATUIN_HOST_USER").unwrap_or_else(|_| whoami::username()),
)
}
#[derive(Debug)]
pub struct XonshSqlite {
pool: SqlitePool,
hostname: String,
}
#[async_trait]
impl Importer for XonshSqlite {
const NAME: &'static str = "xonsh_sqlite";
async fn new() -> Result<Self> {
let db_path = get_db_path(env::var("XONSH_DATA_DIR").ok())?;
let connection_str = db_path.to_str().ok_or_else(|| {
eyre!(
"Invalid path for SQLite database: {}",
db_path.to_string_lossy()
)
})?;
let pool = SqlitePool::connect(connection_str).await?;
let hostname = get_hostname();
Ok(XonshSqlite { pool, hostname })
}
async fn entries(&mut self) -> Result<usize> {
let query = "SELECT COUNT(*) FROM xonsh_history";
let row = sqlx::query(query).fetch_one(&self.pool).await?;
let count: u32 = row.get(0);
Ok(count as usize)
}
async fn load(self, loader: &mut impl Loader) -> Result<()> {
let query = r#"
SELECT inp, rtn, tsb, tse, cwd,
MIN(tsb) OVER (PARTITION BY sessionid) AS session_start
FROM xonsh_history
ORDER BY rowid
"#;
let mut entries = sqlx::query_as::<_, HistDbEntry>(query).fetch(&self.pool);
let mut count = 0;
while let Some(entry) = entries.try_next().await? {
let hist = entry.into_hist_with_hostname(self.hostname.clone());
loader.push(hist).await?;
count += 1;
}
println!("Loaded: {count}");
Ok(())
}
}
#[cfg(test)]
mod tests {
use time::macros::datetime;
use super::*;
use crate::history::History;
use crate::import::tests::TestLoader;
#[test]
fn test_db_path_xonsh() {
let db_path = get_db_path(Some("/home/user/xonsh_data".to_string())).unwrap();
assert_eq!(
db_path,
PathBuf::from("/home/user/xonsh_data/xonsh-history.sqlite")
);
}
#[tokio::test]
async fn test_import() {
let connection_str = "tests/data/xonsh-history.sqlite";
let xonsh_sqlite = XonshSqlite {
pool: SqlitePool::connect(connection_str).await.unwrap(),
hostname: "box:user".to_string(),
};
let mut loader = TestLoader::default();
xonsh_sqlite.load(&mut loader).await.unwrap();
for (actual, expected) in loader.buf.iter().zip(expected_hist_entries().iter()) {
assert_eq!(actual.timestamp, expected.timestamp);
assert_eq!(actual.command, expected.command);
assert_eq!(actual.cwd, expected.cwd);
assert_eq!(actual.exit, expected.exit);
assert_eq!(actual.duration, expected.duration);
assert_eq!(actual.hostname, expected.hostname);
}
}
fn expected_hist_entries() -> [History; 4] {
[
History::import()
.timestamp(datetime!(2024-02-6 17:56:21.130956288 +00:00:00))
.command("echo hello world!".to_string())
.cwd("/home/user/Documents/code/atuin".to_string())
.exit(0)
.duration(2628564)
.hostname("box:user".to_string())
.build()
.into(),
History::import()
.timestamp(datetime!(2024-02-06 17:56:28.190406144 +00:00:00))
.command("ls -l".to_string())
.cwd("/home/user/Documents/code/atuin".to_string())
.exit(0)
.duration(9371519)
.hostname("box:user".to_string())
.build()
.into(),
History::import()
.timestamp(datetime!(2024-02-06 17:56:46.989020928 +00:00:00))
.command("false".to_string())
.cwd("/home/user/Documents/code/atuin".to_string())
.exit(1)
.duration(17337560)
.hostname("box:user".to_string())
.build()
.into(),
History::import()
.timestamp(datetime!(2024-02-06 17:56:48.218384128 +00:00:00))
.command("exit".to_string())
.cwd("/home/user/Documents/code/atuin".to_string())
.exit(0)
.duration(4599094)
.hostname("box:user".to_string())
.build()
.into(),
]
}
}

Binary file not shown.

View File

@ -0,0 +1,12 @@
{"locs": [ 69, 3371, 3451, 3978],
"index": {"offsets":{"__total__":0,"cmds":[{"__total__":10,"cwd":18,"inp":78,"rtn":96,"ts":[106,125,105]},{"__total__":149,"cwd":157,"inp":217,"rtn":234,"ts":[244,263,243]},9],"env":{"ATUIN_SESSION":314,"BASH_COMPLETIONS":370,"COLORTERM":433,"DBUS_SESSION_BUS_ADDRESS":474,"DESKTOP_SESSION":529,"DISPLAY":550,"GDMSESSION":570,"GIO_LAUNCHED_DESKTOP_FILE":609,"GIO_LAUNCHED_DESKTOP_FILE_PID":704,"GJS_DEBUG_OUTPUT":734,"GJS_DEBUG_TOPICS":764,"GNOME_DESKTOP_SESSION_ID":811,"GNOME_SETUP_DISPLAY":856,"GNOME_SHELL_SESSION_MODE":890,"GTK_MODULES":915,"HOME":942,"IM_CONFIG_PHASE":976,"INVOCATION_ID":998,"JOURNAL_STREAM":1052,"LANG":1071,"LOGNAME":1097,"MANAGERPID":1118,"MOZ_ENABLE_WAYLAND":1148,"PATH":1161,"PWD":1736,"PYENV_DIR":1802,"PYENV_HOOK_PATH":1874,"PYENV_ROOT":2048,"PYENV_SHELL":2086,"PYENV_VERSION":2111,"QT_ACCESSIBILITY":2141,"QT_IM_MODULE":2162,"SESSION_MANAGER":2189,"SHELL":2279,"SHLVL":2303,"SSH_AGENT_LAUNCHER":2330,"SSH_AUTH_SOCK":2364,"SSL_CERT_DIR":2415,"SSL_CERT_FILE":2458,"SYSTEMD_EXEC_PID":2525,"TERM":2541,"TERM_PROGRAM":2575,"TERM_PROGRAM_VERSION":2610,"THREAD_SUBPROCS":2657,"USER":2670,"USERNAME":2689,"WAYLAND_DISPLAY":2715,"WEZTERM_CONFIG_DIR":2750,"WEZTERM_CONFIG_FILE":2806,"WEZTERM_EXECUTABLE":2874,"WEZTERM_EXECUTABLE_DIR":2927,"WEZTERM_PANE":2957,"WEZTERM_UNIX_SOCKET":2986,"XAUTHORITY":3047,"XDG_CONFIG_DIRS":3116,"XDG_CURRENT_DESKTOP":3176,"XDG_DATA_DIRS":3209,"XDG_MENU_PREFIX":3316,"XDG_RUNTIME_DIR":3345,"XDG_SESSION_CLASS":3387,"XDG_SESSION_DESKTOP":3418,"XDG_SESSION_TYPE":3448,"XMODIFIERS":3473,"XONSHRC":3496,"XONSHRC_DIR":3594,"XONSH_CAPTURE_ALWAYS":3674,"XONSH_CONFIG_DIR":3698,"XONSH_DATA_DIR":3747,"XONSH_INTERACTIVE":3805,"XONSH_LOGIN":3825,"XONSH_VERSION":3847,"__total__":296},"locked":3869,"sessionid":3889,"ts":[3936,3956,3935]},"sizes":{"__total__":3978,"cmds":[{"__total__":137,"cwd":51,"inp":9,"rtn":1,"ts":[17,18,40]},{"__total__":136,"cwd":51,"inp":8,"rtn":1,"ts":[17,18,40]},278],"env":{"ATUIN_SESSION":34,"BASH_COMPLETIONS":48,"COLORTERM":11,"DBUS_SESSION_BUS_ADDRESS":34,"DESKTOP_SESSION":8,"DISPLAY":4,"GDMSESSION":8,"GIO_LAUNCHED_DESKTOP_FILE":60,"GIO_LAUNCHED_DESKTOP_FILE_PID":8,"GJS_DEBUG_OUTPUT":8,"GJS_DEBUG_TOPICS":17,"GNOME_DESKTOP_SESSION_ID":20,"GNOME_SETUP_DISPLAY":4,"GNOME_SHELL_SESSION_MODE":8,"GTK_MODULES":17,"HOME":13,"IM_CONFIG_PHASE":3,"INVOCATION_ID":34,"JOURNAL_STREAM":9,"LANG":13,"LOGNAME":5,"MANAGERPID":6,"MOZ_ENABLE_WAYLAND":3,"PATH":566,"PWD":51,"PYENV_DIR":51,"PYENV_HOOK_PATH":158,"PYENV_ROOT":21,"PYENV_SHELL":6,"PYENV_VERSION":8,"QT_ACCESSIBILITY":3,"QT_IM_MODULE":6,"SESSION_MANAGER":79,"SHELL":13,"SHLVL":3,"SSH_AGENT_LAUNCHER":15,"SSH_AUTH_SOCK":33,"SSL_CERT_DIR":24,"SSL_CERT_FILE":45,"SYSTEMD_EXEC_PID":6,"TERM":16,"TERM_PROGRAM":9,"TERM_PROGRAM_VERSION":26,"THREAD_SUBPROCS":3,"USER":5,"USERNAME":5,"WAYLAND_DISPLAY":11,"WEZTERM_CONFIG_DIR":31,"WEZTERM_CONFIG_FILE":44,"WEZTERM_EXECUTABLE":25,"WEZTERM_EXECUTABLE_DIR":12,"WEZTERM_PANE":4,"WEZTERM_UNIX_SOCKET":45,"XAUTHORITY":48,"XDG_CONFIG_DIRS":35,"XDG_CURRENT_DESKTOP":14,"XDG_DATA_DIRS":86,"XDG_MENU_PREFIX":8,"XDG_RUNTIME_DIR":19,"XDG_SESSION_CLASS":6,"XDG_SESSION_DESKTOP":8,"XDG_SESSION_TYPE":9,"XMODIFIERS":10,"XONSHRC":81,"XONSHRC_DIR":54,"XONSH_CAPTURE_ALWAYS":2,"XONSH_CONFIG_DIR":29,"XONSH_DATA_DIR":35,"XONSH_INTERACTIVE":3,"XONSH_LOGIN":3,"XONSH_VERSION":8,"__total__":3561},"locked":5,"sessionid":38,"ts":[18,18,41]}},
"data": {"cmds": [{"cwd": "\/home\/user\/Documents\/code\/atuin\/atuin-client", "inp": "false\n", "rtn": 1, "ts": [1707241291.142516, 1707241291.1527853]
}
, {"cwd": "\/home\/user\/Documents\/code\/atuin\/atuin-client", "inp": "exit\n", "rtn": 0, "ts": [1707241292.271584, 1707241292.2758434]
}
]
, "env": {"ATUIN_SESSION": "018d7f82ad167dc4888ca0bf294d2bfd", "BASH_COMPLETIONS": "\/usr\/share\/bash-completion\/bash_completion", "COLORTERM": "truecolor", "DBUS_SESSION_BUS_ADDRESS": "unix:path=\/run\/user\/1000\/bus", "DESKTOP_SESSION": "ubuntu", "DISPLAY": ":0", "GDMSESSION": "ubuntu", "GIO_LAUNCHED_DESKTOP_FILE": "\/usr\/share\/applications\/org.wezfurlong.wezterm.desktop", "GIO_LAUNCHED_DESKTOP_FILE_PID": "196859", "GJS_DEBUG_OUTPUT": "stderr", "GJS_DEBUG_TOPICS": "JS ERROR;JS LOG", "GNOME_DESKTOP_SESSION_ID": "this-is-deprecated", "GNOME_SETUP_DISPLAY": ":1", "GNOME_SHELL_SESSION_MODE": "ubuntu", "GTK_MODULES": "gail:atk-bridge", "HOME": "\/home\/user", "IM_CONFIG_PHASE": "1", "INVOCATION_ID": "4f121e7ad56c41a6b84aa3cbe1ad61fa", "JOURNAL_STREAM": "8:37187", "LANG": "en_US.UTF-8", "LOGNAME": "user", "MANAGERPID": "2118", "MOZ_ENABLE_WAYLAND": "1", "PATH": "\/home\/user\/.pyenv\/versions\/3.12.0\/bin:\/home\/user\/.pyenv\/libexec:\/home\/user\/.pyenv\/plugins\/python-build\/bin:\/home\/user\/.pyenv\/plugins\/pyenv-virtualenv\/bin:\/home\/user\/.pyenv\/plugins\/pyenv-update\/bin:\/home\/user\/.pyenv\/plugins\/pyenv-doctor\/bin:\/home\/user\/.cargo\/bin:\/home\/user\/.pyenv\/shims:\/home\/user\/.pyenv\/bin:\/home\/user\/bin:\/home\/user\/bin:\/usr\/local\/sbin:\/usr\/local\/bin:\/usr\/sbin:\/usr\/bin:\/sbin:\/bin:\/usr\/games:\/usr\/local\/games:\/snap\/bin:\/snap\/bin:\/home\/user\/.local\/share\/JetBrains\/Toolbox\/scripts", "PWD": "\/home\/user\/Documents\/code\/atuin\/atuin-client", "PYENV_DIR": "\/home\/user\/Documents\/code\/atuin\/atuin-client", "PYENV_HOOK_PATH": "\/home\/user\/.pyenv\/pyenv.d:\/usr\/local\/etc\/pyenv.d:\/etc\/pyenv.d:\/usr\/lib\/pyenv\/hooks:\/home\/user\/.pyenv\/plugins\/pyenv-virtualenv\/etc\/pyenv.d", "PYENV_ROOT": "\/home\/user\/.pyenv", "PYENV_SHELL": "bash", "PYENV_VERSION": "3.12.0", "QT_ACCESSIBILITY": "1", "QT_IM_MODULE": "ibus", "SESSION_MANAGER": "local\/box:@\/tmp\/.ICE-unix\/2452,unix\/box:\/tmp\/.ICE-unix\/2452", "SHELL": "\/bin\/bash", "SHLVL": "1", "SSH_AGENT_LAUNCHER": "gnome-keyring", "SSH_AUTH_SOCK": "\/run\/user\/1000\/keyring\/ssh", "SSL_CERT_DIR": "\/usr\/lib\/ssl\/certs", "SSL_CERT_FILE": "\/usr\/lib\/ssl\/certs\/ca-certificates.crt", "SYSTEMD_EXEC_PID": "2470", "TERM": "xterm-256color", "TERM_PROGRAM": "WezTerm", "TERM_PROGRAM_VERSION": "20240127-113634-bbcac864", "THREAD_SUBPROCS": "1", "USER": "user", "USERNAME": "user", "WAYLAND_DISPLAY": "wayland-0", "WEZTERM_CONFIG_DIR": "\/home\/user\/.config\/wezterm", "WEZTERM_CONFIG_FILE": "\/home\/user\/.config\/wezterm\/wezterm.lua", "WEZTERM_EXECUTABLE": "\/usr\/bin\/wezterm-gui", "WEZTERM_EXECUTABLE_DIR": "\/usr\/bin", "WEZTERM_PANE": "41", "WEZTERM_UNIX_SOCKET": "\/run\/user\/1000\/wezterm\/gui-sock-196859", "XAUTHORITY": "\/run\/user\/1000\/.mutter-Xwaylandauth.T986H2", "XDG_CONFIG_DIRS": "\/etc\/xdg\/xdg-ubuntu:\/etc\/xdg", "XDG_CURRENT_DESKTOP": "ubuntu:GNOME", "XDG_DATA_DIRS": "\/usr\/share\/ubuntu:\/usr\/local\/share\/:\/usr\/share\/:\/var\/lib\/snapd\/desktop", "XDG_MENU_PREFIX": "gnome-", "XDG_RUNTIME_DIR": "\/run\/user\/1000", "XDG_SESSION_CLASS": "user", "XDG_SESSION_DESKTOP": "ubuntu", "XDG_SESSION_TYPE": "wayland", "XMODIFIERS": "@im=ibus", "XONSHRC": "\/etc\/xonsh\/xonshrc:\/home\/user\/.config\/xonsh\/rc.xsh:\/home\/user\/.xonshrc", "XONSHRC_DIR": "\/etc\/xonsh\/rc.d:\/home\/user\/.config\/xonsh\/rc.d", "XONSH_CAPTURE_ALWAYS": "", "XONSH_CONFIG_DIR": "\/home\/user\/.config\/xonsh", "XONSH_DATA_DIR": "\/home\/user\/.local\/share\/xonsh", "XONSH_INTERACTIVE": "1", "XONSH_LOGIN": "1", "XONSH_VERSION": "0.14.2"}
, "locked": false, "sessionid": "82eafbf5-9f43-489a-80d2-61c7dc6ef542", "ts": [1707241286.9361255, 1707241292.3081477]
}
}

View File

@ -0,0 +1,12 @@
{"locs": [ 69, 3372, 3452, 3936],
"index": {"offsets":{"__total__":0,"cmds":[{"__total__":10,"cwd":18,"inp":64,"rtn":94,"ts":[104,124,103]},{"__total__":148,"cwd":156,"inp":202,"rtn":220,"ts":[230,250,229]},9],"env":{"ATUIN_SESSION":300,"BASH_COMPLETIONS":356,"COLORTERM":419,"DBUS_SESSION_BUS_ADDRESS":460,"DESKTOP_SESSION":515,"DISPLAY":536,"GDMSESSION":556,"GIO_LAUNCHED_DESKTOP_FILE":595,"GIO_LAUNCHED_DESKTOP_FILE_PID":690,"GJS_DEBUG_OUTPUT":720,"GJS_DEBUG_TOPICS":750,"GNOME_DESKTOP_SESSION_ID":797,"GNOME_SETUP_DISPLAY":842,"GNOME_SHELL_SESSION_MODE":876,"GTK_MODULES":901,"HOME":928,"IM_CONFIG_PHASE":962,"INVOCATION_ID":984,"JOURNAL_STREAM":1038,"LANG":1057,"LOGNAME":1083,"MANAGERPID":1104,"MOZ_ENABLE_WAYLAND":1134,"PATH":1147,"PWD":1722,"PYENV_DIR":1774,"PYENV_HOOK_PATH":1832,"PYENV_ROOT":2006,"PYENV_SHELL":2044,"PYENV_VERSION":2069,"QT_ACCESSIBILITY":2099,"QT_IM_MODULE":2120,"SESSION_MANAGER":2147,"SHELL":2237,"SHLVL":2261,"SSH_AGENT_LAUNCHER":2288,"SSH_AUTH_SOCK":2322,"SSL_CERT_DIR":2373,"SSL_CERT_FILE":2416,"SYSTEMD_EXEC_PID":2483,"TERM":2499,"TERM_PROGRAM":2533,"TERM_PROGRAM_VERSION":2568,"THREAD_SUBPROCS":2615,"USER":2628,"USERNAME":2647,"WAYLAND_DISPLAY":2673,"WEZTERM_CONFIG_DIR":2708,"WEZTERM_CONFIG_FILE":2764,"WEZTERM_EXECUTABLE":2832,"WEZTERM_EXECUTABLE_DIR":2885,"WEZTERM_PANE":2915,"WEZTERM_UNIX_SOCKET":2944,"XAUTHORITY":3005,"XDG_CONFIG_DIRS":3074,"XDG_CURRENT_DESKTOP":3134,"XDG_DATA_DIRS":3167,"XDG_MENU_PREFIX":3274,"XDG_RUNTIME_DIR":3303,"XDG_SESSION_CLASS":3345,"XDG_SESSION_DESKTOP":3376,"XDG_SESSION_TYPE":3406,"XMODIFIERS":3431,"XONSHRC":3454,"XONSHRC_DIR":3552,"XONSH_CAPTURE_ALWAYS":3632,"XONSH_CONFIG_DIR":3656,"XONSH_DATA_DIR":3705,"XONSH_INTERACTIVE":3763,"XONSH_LOGIN":3783,"XONSH_VERSION":3805,"__total__":282},"locked":3827,"sessionid":3847,"ts":[3894,3914,3893]},"sizes":{"__total__":3936,"cmds":[{"__total__":136,"cwd":37,"inp":21,"rtn":1,"ts":[18,18,41]},{"__total__":123,"cwd":37,"inp":9,"rtn":1,"ts":[18,17,40]},264],"env":{"ATUIN_SESSION":34,"BASH_COMPLETIONS":48,"COLORTERM":11,"DBUS_SESSION_BUS_ADDRESS":34,"DESKTOP_SESSION":8,"DISPLAY":4,"GDMSESSION":8,"GIO_LAUNCHED_DESKTOP_FILE":60,"GIO_LAUNCHED_DESKTOP_FILE_PID":8,"GJS_DEBUG_OUTPUT":8,"GJS_DEBUG_TOPICS":17,"GNOME_DESKTOP_SESSION_ID":20,"GNOME_SETUP_DISPLAY":4,"GNOME_SHELL_SESSION_MODE":8,"GTK_MODULES":17,"HOME":13,"IM_CONFIG_PHASE":3,"INVOCATION_ID":34,"JOURNAL_STREAM":9,"LANG":13,"LOGNAME":5,"MANAGERPID":6,"MOZ_ENABLE_WAYLAND":3,"PATH":566,"PWD":37,"PYENV_DIR":37,"PYENV_HOOK_PATH":158,"PYENV_ROOT":21,"PYENV_SHELL":6,"PYENV_VERSION":8,"QT_ACCESSIBILITY":3,"QT_IM_MODULE":6,"SESSION_MANAGER":79,"SHELL":13,"SHLVL":3,"SSH_AGENT_LAUNCHER":15,"SSH_AUTH_SOCK":33,"SSL_CERT_DIR":24,"SSL_CERT_FILE":45,"SYSTEMD_EXEC_PID":6,"TERM":16,"TERM_PROGRAM":9,"TERM_PROGRAM_VERSION":26,"THREAD_SUBPROCS":3,"USER":5,"USERNAME":5,"WAYLAND_DISPLAY":11,"WEZTERM_CONFIG_DIR":31,"WEZTERM_CONFIG_FILE":44,"WEZTERM_EXECUTABLE":25,"WEZTERM_EXECUTABLE_DIR":12,"WEZTERM_PANE":4,"WEZTERM_UNIX_SOCKET":45,"XAUTHORITY":48,"XDG_CONFIG_DIRS":35,"XDG_CURRENT_DESKTOP":14,"XDG_DATA_DIRS":86,"XDG_MENU_PREFIX":8,"XDG_RUNTIME_DIR":19,"XDG_SESSION_CLASS":6,"XDG_SESSION_DESKTOP":8,"XDG_SESSION_TYPE":9,"XMODIFIERS":10,"XONSHRC":81,"XONSHRC_DIR":54,"XONSH_CAPTURE_ALWAYS":2,"XONSH_CONFIG_DIR":29,"XONSH_DATA_DIR":35,"XONSH_INTERACTIVE":3,"XONSH_LOGIN":3,"XONSH_VERSION":8,"__total__":3533},"locked":5,"sessionid":38,"ts":[18,18,41]}},
"data": {"cmds": [{"cwd": "\/home\/user\/Documents\/code\/atuin", "inp": "echo hello world!\n", "rtn": 0, "ts": [1707193079.4782722, 1707193079.4829233]
}
, {"cwd": "\/home\/user\/Documents\/code\/atuin", "inp": "ls -l\n", "rtn": 0, "ts": [1707193081.7063284, 1707193081.727617]
}
]
, "env": {"ATUIN_SESSION": "018d7ca2e953742e9826012f30115040", "BASH_COMPLETIONS": "\/usr\/share\/bash-completion\/bash_completion", "COLORTERM": "truecolor", "DBUS_SESSION_BUS_ADDRESS": "unix:path=\/run\/user\/1000\/bus", "DESKTOP_SESSION": "ubuntu", "DISPLAY": ":0", "GDMSESSION": "ubuntu", "GIO_LAUNCHED_DESKTOP_FILE": "\/usr\/share\/applications\/org.wezfurlong.wezterm.desktop", "GIO_LAUNCHED_DESKTOP_FILE_PID": "196859", "GJS_DEBUG_OUTPUT": "stderr", "GJS_DEBUG_TOPICS": "JS ERROR;JS LOG", "GNOME_DESKTOP_SESSION_ID": "this-is-deprecated", "GNOME_SETUP_DISPLAY": ":1", "GNOME_SHELL_SESSION_MODE": "ubuntu", "GTK_MODULES": "gail:atk-bridge", "HOME": "\/home\/user", "IM_CONFIG_PHASE": "1", "INVOCATION_ID": "4f121e7ad56c41a6b84aa3cbe1ad61fa", "JOURNAL_STREAM": "8:37187", "LANG": "en_US.UTF-8", "LOGNAME": "user", "MANAGERPID": "2118", "MOZ_ENABLE_WAYLAND": "1", "PATH": "\/home\/user\/.pyenv\/versions\/3.12.0\/bin:\/home\/user\/.pyenv\/libexec:\/home\/user\/.pyenv\/plugins\/python-build\/bin:\/home\/user\/.pyenv\/plugins\/pyenv-virtualenv\/bin:\/home\/user\/.pyenv\/plugins\/pyenv-update\/bin:\/home\/user\/.pyenv\/plugins\/pyenv-doctor\/bin:\/home\/user\/.cargo\/bin:\/home\/user\/.pyenv\/shims:\/home\/user\/.pyenv\/bin:\/home\/user\/bin:\/home\/user\/bin:\/usr\/local\/sbin:\/usr\/local\/bin:\/usr\/sbin:\/usr\/bin:\/sbin:\/bin:\/usr\/games:\/usr\/local\/games:\/snap\/bin:\/snap\/bin:\/home\/user\/.local\/share\/JetBrains\/Toolbox\/scripts", "PWD": "\/home\/user\/Documents\/code\/atuin", "PYENV_DIR": "\/home\/user\/Documents\/code\/atuin", "PYENV_HOOK_PATH": "\/home\/user\/.pyenv\/pyenv.d:\/usr\/local\/etc\/pyenv.d:\/etc\/pyenv.d:\/usr\/lib\/pyenv\/hooks:\/home\/user\/.pyenv\/plugins\/pyenv-virtualenv\/etc\/pyenv.d", "PYENV_ROOT": "\/home\/user\/.pyenv", "PYENV_SHELL": "bash", "PYENV_VERSION": "3.12.0", "QT_ACCESSIBILITY": "1", "QT_IM_MODULE": "ibus", "SESSION_MANAGER": "local\/box:@\/tmp\/.ICE-unix\/2452,unix\/box:\/tmp\/.ICE-unix\/2452", "SHELL": "\/bin\/bash", "SHLVL": "1", "SSH_AGENT_LAUNCHER": "gnome-keyring", "SSH_AUTH_SOCK": "\/run\/user\/1000\/keyring\/ssh", "SSL_CERT_DIR": "\/usr\/lib\/ssl\/certs", "SSL_CERT_FILE": "\/usr\/lib\/ssl\/certs\/ca-certificates.crt", "SYSTEMD_EXEC_PID": "2470", "TERM": "xterm-256color", "TERM_PROGRAM": "WezTerm", "TERM_PROGRAM_VERSION": "20240127-113634-bbcac864", "THREAD_SUBPROCS": "1", "USER": "user", "USERNAME": "user", "WAYLAND_DISPLAY": "wayland-0", "WEZTERM_CONFIG_DIR": "\/home\/user\/.config\/wezterm", "WEZTERM_CONFIG_FILE": "\/home\/user\/.config\/wezterm\/wezterm.lua", "WEZTERM_EXECUTABLE": "\/usr\/bin\/wezterm-gui", "WEZTERM_EXECUTABLE_DIR": "\/usr\/bin", "WEZTERM_PANE": "38", "WEZTERM_UNIX_SOCKET": "\/run\/user\/1000\/wezterm\/gui-sock-196859", "XAUTHORITY": "\/run\/user\/1000\/.mutter-Xwaylandauth.T986H2", "XDG_CONFIG_DIRS": "\/etc\/xdg\/xdg-ubuntu:\/etc\/xdg", "XDG_CURRENT_DESKTOP": "ubuntu:GNOME", "XDG_DATA_DIRS": "\/usr\/share\/ubuntu:\/usr\/local\/share\/:\/usr\/share\/:\/var\/lib\/snapd\/desktop", "XDG_MENU_PREFIX": "gnome-", "XDG_RUNTIME_DIR": "\/run\/user\/1000", "XDG_SESSION_CLASS": "user", "XDG_SESSION_DESKTOP": "ubuntu", "XDG_SESSION_TYPE": "wayland", "XMODIFIERS": "@im=ibus", "XONSHRC": "\/etc\/xonsh\/xonshrc:\/home\/user\/.config\/xonsh\/rc.xsh:\/home\/user\/.xonshrc", "XONSHRC_DIR": "\/etc\/xonsh\/rc.d:\/home\/user\/.config\/xonsh\/rc.d", "XONSH_CAPTURE_ALWAYS": "", "XONSH_CONFIG_DIR": "\/home\/user\/.config\/xonsh", "XONSH_DATA_DIR": "\/home\/user\/.local\/share\/xonsh", "XONSH_INTERACTIVE": "1", "XONSH_LOGIN": "1", "XONSH_VERSION": "0.14.2"}
, "locked": false, "sessionid": "de16af90-9148-4461-8df3-5b5659c6420d", "ts": [1707193067.8615997, 1707193089.2513068]
}
}

View File

@ -9,8 +9,8 @@ use atuin_client::{
database::Database,
history::History,
import::{
bash::Bash, fish::Fish, nu::Nu, nu_histdb::NuHistDb, resh::Resh, zsh::Zsh,
zsh_histdb::ZshHistDb, Importer, Loader,
bash::Bash, fish::Fish, nu::Nu, nu_histdb::NuHistDb, resh::Resh, xonsh::Xonsh,
xonsh_sqlite::XonshSqlite, zsh::Zsh, zsh_histdb::ZshHistDb, Importer, Loader,
},
};
@ -34,6 +34,10 @@ pub enum Cmd {
Nu,
/// Import history from the nu history file
NuHistDb,
/// Import history from xonsh json files
Xonsh,
/// Import history from xonsh sqlite db
XonshSqlite,
}
const BATCH_SIZE: usize = 100;
@ -97,6 +101,8 @@ impl Cmd {
Self::Fish => import::<Fish, DB>(db).await,
Self::Nu => import::<Nu, DB>(db).await,
Self::NuHistDb => import::<NuHistDb, DB>(db).await,
Self::Xonsh => import::<Xonsh, DB>(db).await,
Self::XonshSqlite => import::<XonshSqlite, DB>(db).await,
}
}
}