atuin_client/import/
resh.rs1use 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, };
102 let entry = match serde_json::from_str::<ReshEntry>(s) {
103 Ok(e) => e,
104 Err(_) => continue, };
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 .session(uuid_v7().as_simple().to_string());
134
135 h.push(imported.build().into()).await?;
136 }
137
138 Ok(())
139 }
140}