diff options
Diffstat (limited to 'atuin-client/src/import/bash.rs')
-rw-r--r-- | atuin-client/src/import/bash.rs | 121 |
1 files changed, 88 insertions, 33 deletions
diff --git a/atuin-client/src/import/bash.rs b/atuin-client/src/import/bash.rs index d5fbef46..1a171625 100644 --- a/atuin-client/src/import/bash.rs +++ b/atuin-client/src/import/bash.rs @@ -1,63 +1,77 @@ -use std::io::{BufRead, BufReader}; -use std::{fs::File, path::Path}; +use std::{ + fs::File, + io::{BufRead, BufReader, Read, Seek}, + path::{Path, PathBuf}, +}; +use directories::UserDirs; use eyre::{eyre, Result}; -use super::count_lines; +use super::{count_lines, Importer}; use crate::history::History; #[derive(Debug)] -pub struct Bash { - file: BufReader<File>, - - pub loc: u64, - pub counter: i64, +pub struct Bash<R> { + file: BufReader<R>, + strbuf: String, + loc: usize, + counter: i64, } -impl Bash { - pub fn new(path: impl AsRef<Path>) -> Result<Self> { - let file = File::open(path)?; - let mut buf = BufReader::new(file); +impl<R: Read + Seek> Bash<R> { + fn new(r: R) -> Result<Self> { + let mut buf = BufReader::new(r); let loc = count_lines(&mut buf)?; Ok(Self { file: buf, - loc: loc as u64, + strbuf: String::new(), + loc, counter: 0, }) } +} - fn read_line(&mut self) -> Option<Result<String>> { - let mut line = String::new(); +impl Importer for Bash<File> { + const NAME: &'static str = "bash"; - match self.file.read_line(&mut line) { - Ok(0) => None, - Ok(_) => Some(Ok(line)), - Err(e) => Some(Err(eyre!("failed to read line: {}", e))), // we can skip past things like invalid utf8 - } + fn histpath() -> Result<PathBuf> { + let user_dirs = UserDirs::new().unwrap(); + let home_dir = user_dirs.home_dir(); + + Ok(home_dir.join(".bash_history")) + } + + fn parse(path: impl AsRef<Path>) -> Result<Self> { + Self::new(File::open(path)?) } } -impl Iterator for Bash { +impl<R: Read> Iterator for Bash<R> { type Item = Result<History>; fn next(&mut self) -> Option<Self::Item> { - let line = self.read_line()?; - - if let Err(e) = line { - return Some(Err(e)); // :( + self.strbuf.clear(); + match self.file.read_line(&mut self.strbuf) { + Ok(0) => return None, + Ok(_) => (), + Err(e) => return Some(Err(eyre!("failed to read line: {}", e))), // we can skip past things like invalid utf8 } - let mut line = line.unwrap(); + self.loc -= 1; - while line.ends_with("\\\n") { - let next_line = self.read_line()?; - - if next_line.is_err() { + while self.strbuf.ends_with("\\\n") { + if self.file.read_line(&mut self.strbuf).is_err() { + // There's a chance that the last line of a command has invalid + // characters, the only safe thing to do is break :/ + // usually just invalid utf8 or smth + // however, we really need to avoid missing history, so it's + // better to have some items that should have been part of + // something else, than to miss things. So break. break; - } + }; - line.push_str(next_line.unwrap().as_str()); + self.loc -= 1; } let time = chrono::Utc::now(); @@ -68,7 +82,7 @@ impl Iterator for Bash { Some(Ok(History::new( time, - line.trim_end().to_string(), + self.strbuf.trim_end().to_string(), String::from("unknown"), -1, -1, @@ -76,4 +90,45 @@ impl Iterator for Bash { None, ))) } + + fn size_hint(&self) -> (usize, Option<usize>) { + (0, Some(self.loc)) + } +} + +#[cfg(test)] +mod tests { + use std::io::Cursor; + + use super::Bash; + + #[test] + fn test_parse_file() { + let input = r"cargo install atuin +cargo install atuin; \ +cargo update +cargo :b̷i̶t̴r̵o̴t̴ ̵i̷s̴ ̷r̶e̵a̸l̷ +"; + + let cursor = Cursor::new(input); + let mut bash = Bash::new(cursor).unwrap(); + assert_eq!(bash.loc, 4); + assert_eq!(bash.size_hint(), (0, Some(4))); + + assert_eq!( + &bash.next().unwrap().unwrap().command, + "cargo install atuin" + ); + assert_eq!( + &bash.next().unwrap().unwrap().command, + "cargo install atuin; \\\ncargo update" + ); + assert_eq!( + &bash.next().unwrap().unwrap().command, + "cargo :b̷i̶t̴r̵o̴t̴ ̵i̷s̴ ̷r̶e̵a̸l̷" + ); + assert!(bash.next().is_none()); + + assert_eq!(bash.size_hint(), (0, Some(0))); + } } |