atuin_client/import/
fish.rs1use std::path::PathBuf;
5
6use async_trait::async_trait;
7use directories::BaseDirs;
8use eyre::{eyre, Result};
9use time::OffsetDateTime;
10
11use super::{unix_byte_lines, Importer, Loader};
12use crate::history::History;
13use crate::import::read_to_end;
14
15#[derive(Debug)]
16pub struct Fish {
17 bytes: Vec<u8>,
18}
19
20fn default_histpath() -> Result<PathBuf> {
22 let base = BaseDirs::new().ok_or_else(|| eyre!("could not determine data directory"))?;
23 let data = std::env::var("XDG_DATA_HOME").map_or_else(
24 |_| base.home_dir().join(".local").join("share"),
25 PathBuf::from,
26 );
27
28 let session = std::env::var("fish_history").unwrap_or_else(|_| String::from("fish"));
31 let session = if session == "default" {
32 String::from("fish")
33 } else {
34 session
35 };
36
37 let mut histpath = data.join("fish");
38 histpath.push(format!("{session}_history"));
39
40 if histpath.exists() {
41 Ok(histpath)
42 } else {
43 Err(eyre!("Could not find history file."))
44 }
45}
46
47#[async_trait]
48impl Importer for Fish {
49 const NAME: &'static str = "fish";
50
51 async fn new() -> Result<Self> {
52 let bytes = read_to_end(default_histpath()?)?;
53 Ok(Self { bytes })
54 }
55
56 async fn entries(&mut self) -> Result<usize> {
57 Ok(super::count_lines(&self.bytes))
58 }
59
60 async fn load(self, loader: &mut impl Loader) -> Result<()> {
61 let now = OffsetDateTime::now_utc();
62 let mut time: Option<OffsetDateTime> = None;
63 let mut cmd: Option<String> = None;
64
65 for b in unix_byte_lines(&self.bytes) {
66 let s = match std::str::from_utf8(b) {
67 Ok(s) => s,
68 Err(_) => continue, };
70
71 if let Some(c) = s.strip_prefix("- cmd: ") {
72 if let Some(cmd) = cmd.take() {
74 let time = time.unwrap_or(now);
75 let entry = History::import().timestamp(time).command(cmd);
76
77 loader.push(entry.build().into()).await?;
78 }
79
80 let c = c.replace(r"\\", r"\");
83 let c = c.replace(r"\n", "\n");
85 cmd = Some(c);
88 } else if let Some(t) = s.strip_prefix(" when: ") {
89 if let Ok(t) = t.parse::<i64>() {
91 time = Some(OffsetDateTime::from_unix_timestamp(t)?);
92 }
93 } else {
94 }
96 }
97
98 if let Some(cmd) = cmd.take() {
100 let time = time.unwrap_or(now);
101 let entry = History::import().timestamp(time).command(cmd);
102
103 loader.push(entry.build().into()).await?;
104 }
105
106 Ok(())
107 }
108}
109
110#[cfg(test)]
111mod test {
112
113 use crate::import::{tests::TestLoader, Importer};
114
115 use super::Fish;
116
117 #[tokio::test]
118 async fn parse_complex() {
119 let bytes = r#"- cmd: history --help
121 when: 1639162832
122- cmd: cat ~/.bash_history
123 when: 1639162851
124 paths:
125 - ~/.bash_history
126- cmd: ls ~/.local/share/fish/fish_history
127 when: 1639162890
128 paths:
129 - ~/.local/share/fish/fish_history
130- cmd: cat ~/.local/share/fish/fish_history
131 when: 1639162893
132 paths:
133 - ~/.local/share/fish/fish_history
134ERROR
135- CORRUPTED: ENTRY
136 CONTINUE:
137 - AS
138 - NORMAL
139- cmd: echo "foo" \\\n'bar' baz
140 when: 1639162933
141- cmd: cat ~/.local/share/fish/fish_history
142 when: 1639162939
143 paths:
144 - ~/.local/share/fish/fish_history
145- cmd: echo "\\"" \\\\ "\\\\"
146 when: 1639163063
147- cmd: cat ~/.local/share/fish/fish_history
148 when: 1639163066
149 paths:
150 - ~/.local/share/fish/fish_history
151"#
152 .as_bytes()
153 .to_owned();
154
155 let fish = Fish { bytes };
156
157 let mut loader = TestLoader::default();
158 fish.load(&mut loader).await.unwrap();
159 let mut history = loader.buf.into_iter();
160
161 macro_rules! fishtory {
163 ($timestamp:expr, $command:expr) => {
164 let h = history.next().expect("missing entry in history");
165 assert_eq!(h.command.as_str(), $command);
166 assert_eq!(h.timestamp.unix_timestamp(), $timestamp);
167 };
168 }
169
170 fishtory!(1639162832, "history --help");
171 fishtory!(1639162851, "cat ~/.bash_history");
172 fishtory!(1639162890, "ls ~/.local/share/fish/fish_history");
173 fishtory!(1639162893, "cat ~/.local/share/fish/fish_history");
174 fishtory!(1639162933, "echo \"foo\" \\\n'bar' baz");
175 fishtory!(1639162939, "cat ~/.local/share/fish/fish_history");
176 fishtory!(1639163063, r#"echo "\"" \\ "\\""#);
177 fishtory!(1639163066, "cat ~/.local/share/fish/fish_history");
178 }
179}