atuin_client/import/
resh.rs

1use std::path::PathBuf;
2
3use async_trait::async_trait;
4use directories::UserDirs;
5use eyre::{eyre, Result};
6use serde::Deserialize;
7
8use atuin_common::utils::uuid_v7;
9use time::OffsetDateTime;
10
11use super::{get_histpath, unix_byte_lines, Importer, Loader};
12use crate::history::History;
13use crate::import::read_to_end;
14
15#[derive(Deserialize, Debug)]
16#[serde(rename_all = "camelCase")]
17pub struct ReshEntry {
18    pub cmd_line: String,
19    pub exit_code: i64,
20    pub shell: String,
21    pub uname: String,
22    pub session_id: String,
23    pub home: String,
24    pub lang: String,
25    pub lc_all: String,
26    pub login: String,
27    pub pwd: String,
28    pub pwd_after: String,
29    pub shell_env: String,
30    pub term: String,
31    pub real_pwd: String,
32    pub real_pwd_after: String,
33    pub pid: i64,
34    pub session_pid: i64,
35    pub host: String,
36    pub hosttype: String,
37    pub ostype: String,
38    pub machtype: String,
39    pub shlvl: i64,
40    pub timezone_before: String,
41    pub timezone_after: String,
42    pub realtime_before: f64,
43    pub realtime_after: f64,
44    pub realtime_before_local: f64,
45    pub realtime_after_local: f64,
46    pub realtime_duration: f64,
47    pub realtime_since_session_start: f64,
48    pub realtime_since_boot: f64,
49    pub git_dir: String,
50    pub git_real_dir: String,
51    pub git_origin_remote: String,
52    pub git_dir_after: String,
53    pub git_real_dir_after: String,
54    pub git_origin_remote_after: String,
55    pub machine_id: String,
56    pub os_release_id: String,
57    pub os_release_version_id: String,
58    pub os_release_id_like: String,
59    pub os_release_name: String,
60    pub os_release_pretty_name: String,
61    pub resh_uuid: String,
62    pub resh_version: String,
63    pub resh_revision: String,
64    pub parts_merged: bool,
65    pub recalled: bool,
66    pub recall_last_cmd_line: String,
67    pub cols: String,
68    pub lines: String,
69}
70
71#[derive(Debug)]
72pub struct Resh {
73    bytes: Vec<u8>,
74}
75
76fn default_histpath() -> Result<PathBuf> {
77    let user_dirs = UserDirs::new().ok_or_else(|| eyre!("could not find user directories"))?;
78    let home_dir = user_dirs.home_dir();
79
80    Ok(home_dir.join(".resh_history.json"))
81}
82
83#[async_trait]
84impl Importer for Resh {
85    const NAME: &'static str = "resh";
86
87    async fn new() -> Result<Self> {
88        let bytes = read_to_end(get_histpath(default_histpath)?)?;
89        Ok(Self { bytes })
90    }
91
92    async fn entries(&mut self) -> Result<usize> {
93        Ok(super::count_lines(&self.bytes))
94    }
95
96    async fn load(self, h: &mut impl Loader) -> Result<()> {
97        for b in unix_byte_lines(&self.bytes) {
98            let s = match std::str::from_utf8(b) {
99                Ok(s) => s,
100                Err(_) => continue, // we can skip past things like invalid utf8
101            };
102            let entry = match serde_json::from_str::<ReshEntry>(s) {
103                Ok(e) => e,
104                Err(_) => continue, // skip invalid json :shrug:
105            };
106
107            #[allow(clippy::cast_possible_truncation)]
108            #[allow(clippy::cast_sign_loss)]
109            let timestamp = {
110                let secs = entry.realtime_before.floor() as i64;
111                let nanosecs = (entry.realtime_before.fract() * 1_000_000_000_f64).round() as i64;
112                OffsetDateTime::from_unix_timestamp(secs)? + time::Duration::nanoseconds(nanosecs)
113            };
114            #[allow(clippy::cast_possible_truncation)]
115            #[allow(clippy::cast_sign_loss)]
116            let duration = {
117                let secs = entry.realtime_after.floor() as i64;
118                let nanosecs = (entry.realtime_after.fract() * 1_000_000_000_f64).round() as i64;
119                let base = OffsetDateTime::from_unix_timestamp(secs)?
120                    + time::Duration::nanoseconds(nanosecs);
121                let difference = base - timestamp;
122                difference.whole_nanoseconds() as i64
123            };
124
125            let imported = History::import()
126                .command(entry.cmd_line)
127                .timestamp(timestamp)
128                .duration(duration)
129                .exit(entry.exit_code)
130                .cwd(entry.pwd)
131                .hostname(entry.host)
132                // CHECK: should we add uuid here? It's not set in the other importers
133                .session(uuid_v7().as_simple().to_string());
134
135            h.push(imported.build().into()).await?;
136        }
137
138        Ok(())
139    }
140}