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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
// import old shell history!
// automatically hoover up all that we can find
use std::fs::File;
use std::io::{BufRead, BufReader};
use eyre::{eyre, Result};
use crate::local::history::History;
#[derive(Debug)]
pub struct ImportZsh {
file: BufReader<File>,
pub loc: u64,
}
// this could probably be sped up
fn count_lines(path: &str) -> Result<usize> {
let file = File::open(path)?;
let buf = BufReader::new(file);
Ok(buf.lines().count())
}
impl ImportZsh {
pub fn new(path: &str) -> Result<ImportZsh> {
let loc = count_lines(path)?;
let file = File::open(path)?;
let buf = BufReader::new(file);
Ok(ImportZsh {
file: buf,
loc: loc as u64,
})
}
}
fn trim_newline(s: &str) -> String {
let mut s = String::from(s);
if s.ends_with('\n') {
s.pop();
if s.ends_with('\r') {
s.pop();
}
}
s
}
fn parse_extended(line: String) -> History {
let line = line.replacen(": ", "", 2);
let mut split = line.splitn(2, ":");
let time = split.next().unwrap_or("-1");
let time = time
.parse::<i64>()
.unwrap_or(chrono::Utc::now().timestamp_nanos());
let duration = split.next().unwrap(); // might be 0;the command
let mut split = duration.split(";");
let duration = split.next().unwrap_or("-1"); // should just be the 0
let duration = duration.parse::<i64>().unwrap_or(-1);
let command = split.next().unwrap();
// use nanos, because why the hell not? we won't display them.
History::new(
time * 1_000_000_000,
trim_newline(command),
String::from("unknown"),
-1,
duration * 1_000_000_000,
None,
None,
)
}
impl Iterator for ImportZsh {
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,
Err(e) => Some(Err(eyre!("failed to parse line: {}", e))),
Ok(_) => {
let extended = line.starts_with(":");
if extended {
Some(Ok(parse_extended(line)))
} else {
Some(Ok(History::new(
chrono::Utc::now().timestamp_nanos(), // what else? :/
trim_newline(line.as_str()),
String::from("unknown"),
-1,
-1,
None,
None,
)))
}
}
}
}
}
|