aboutsummaryrefslogtreecommitdiffstats
path: root/src/local/import.rs
blob: ddccc75a25ac7d8dd4aef03ef16d9b2f4c625933 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// import old shell history!
// automatically hoover up all that we can find

use std::io::{BufRead, BufReader, Seek, SeekFrom};
use std::{fs::File, path::Path};

use eyre::{Result, WrapErr};

use super::history::History;

#[derive(Debug)]
pub struct Zsh {
    file: BufReader<File>,

    pub loc: u64,
}

// this could probably be sped up
fn count_lines(buf: &mut BufReader<File>) -> Result<usize> {
    let lines = buf.lines().count();
    buf.seek(SeekFrom::Start(0))?;

    Ok(lines)
}

impl Zsh {
    pub fn new(path: impl AsRef<Path>) -> Result<Self> {
        let file = File::open(path)?;
        let mut buf = BufReader::new(file);
        let loc = count_lines(&mut buf)?;

        Ok(Self {
            file: buf,
            loc: loc as u64,
        })
    }
}

fn parse_extended(line: &str) -> History {
    let line = line.replacen(": ", "", 2);
    let (time, duration) = line.split_once(':').unwrap();
    let (duration, command) = duration.split_once(';').unwrap();

    let time = time.parse::<i64>().map_or_else(
        |_| chrono::Utc::now().timestamp_nanos(),
        |t| t * 1_000_000_000,
    );

    let duration = duration.parse::<i64>().map_or(-1, |t| t * 1_000_000_000);

    // use nanos, because why the hell not? we won't display them.
    History::new(
        time,
        command.trim_end().to_string(),
        String::from("unknown"),
        -1,
        duration,
        None,
        None,
    )
}

impl Iterator for Zsh {
    type Item = Result<History>;

    fn next(&mut self) -> Option<Self::Item> {
        // ZSH extended history records the timestamp + command duration
        // These lines begin with :
        // So, if the line begins with :, parse it. Otherwise it's just
        // the command
        let mut line = String::new();

        match self.file.read_line(&mut line) {
            Ok(0) => None,
            Ok(_) => {
                let extended = line.starts_with(':');

                if extended {
                    Some(Ok(parse_extended(line.as_str())))
                } else {
                    Some(Ok(History::new(
                        chrono::Utc::now().timestamp_nanos(), // what else? :/
                        line.trim_end().to_string(),
                        String::from("unknown"),
                        -1,
                        -1,
                        None,
                        None,
                    )))
                }
            }
            Err(e) => Some(Err(e).wrap_err("failed to parse line")),
        }
    }
}