nu_hist/
lib.rs

1#![deny(clippy::implicit_return)]
2#![allow(clippy::needless_return)]
3use rusqlite::{ Connection, Result };
4// use rand::Rng;
5use std::process;
6use chrono::*;
7use inline_colorization::*;
8use comfy_table::*;
9
10pub struct Config {
11  pub path: String,
12  pub analysis: String,
13}
14impl Config {
15  pub fn new(args: &[String]) -> Result<Config> {
16    if args.len() < 3 {
17      eprintln!(
18        "{color_red}{style_bold}Not enough arguments: {style_reset}{color_reset}nu-hist [path to history.sqlite3 file] [year as number | 'all']"
19      );
20      process::exit(1);
21    }
22    let path = args[1].clone();
23    let analysis = args[2].clone();
24    return Ok(Config { path, analysis });
25  }
26}
27
28pub fn year(conn: Connection, year: String) -> Result<()> {
29  let year: i32 = year.to_string().parse::<i32>().unwrap();
30  let (start, end) = year_to_unix(year);
31
32  let mut content: rusqlite::Statement<'_> = conn.prepare(
33    "select * from history where start_timestamp >= ?1 AND start_timestamp <= ?2"
34  )?;
35  let mut rows = content.query([start, end])?;
36
37  let mut arr: Vec<String> = Vec::new();
38  while let Some(row) = rows.next()? {
39    let command: String = row.get(1)?;
40    arr.push(command);
41  }
42
43  let _ = top_ten_dur(&conn, start, end);
44  let mut head = Table::new();
45  head.set_header(
46    vec![
47      Cell::new("Year: ".to_string() + &year.to_string())
48        .add_attribute(Attribute::Bold)
49        .fg(Color::Magenta),
50      Cell::new("Total Commands: ".to_string() + &arr.len().to_string())
51        .add_attribute(Attribute::Bold)
52        .fg(Color::Magenta)
53    ]
54  );
55  println!("{}", head);
56  println!("{}", table(top_ten_dur(&conn, start, end).unwrap(), arr.len() as i64));
57  return Ok(());
58}
59
60pub fn all(conn: Connection) -> Result<()> {
61  let mut content: rusqlite::Statement<'_> = conn.prepare("SELECT COUNT(*) from history")?;
62  let mut rows = content.query([])?;
63  if let Some(row) = rows.next()? {
64    let len: i64 = row.get(0)?;
65    let mut head = Table::new();
66    head.set_header(
67      vec![
68        Cell::new("Total Commands: ".to_string() + &len.to_string())
69          .add_attribute(Attribute::Bold)
70          .fg(Color::Magenta)
71      ]
72    );
73    println!("{}", head);
74    println!("{}", table(top_ten_dur(&conn, 0, i64::MAX).unwrap(), len));
75  }
76  return Ok(());
77}
78
79fn top_ten_dur(conn: &Connection, start: i64, end: i64) -> Result<Vec<(String, String)>> {
80  let mut content: rusqlite::Statement<'_> = conn.prepare(
81    "SELECT
82      CASE WHEN instr(command_line, ' ') > 0
83          THEN substr(command_line, 1, instr(command_line, ' ') - 1)
84          ELSE command_line
85      END AS first_word,
86      CAST(count(*) AS VARCHAR) AS count
87  FROM history
88  WHERE start_timestamp >= ?1 AND start_timestamp <= ?2
89  GROUP BY first_word
90  ORDER BY CAST(count(*) AS INTEGER) DESC
91  LIMIT 10;"
92  )?;
93  let mut rows = content.query([start, end])?;
94  let mut arr: Vec<(String, String)> = Vec::new();
95  while let Some(row) = rows.next()? {
96    let a: String = row.get(0)?;
97    let b: String = row.get(1)?;
98    arr.push((a, b));
99  }
100  return Ok(arr);
101}
102fn table(arr: Vec<(String, String)>, total: i64) -> Table {
103  let mut table = Table::new();
104  table.set_header(
105    vec![
106      Cell::new("#").add_attribute(Attribute::Bold).fg(Color::Green),
107      Cell::new("Command").add_attribute(Attribute::Bold).fg(Color::Green),
108      Cell::new("Count").add_attribute(Attribute::Bold).fg(Color::Green),
109      Cell::new("Bar").add_attribute(Attribute::Bold).fg(Color::Green)
110    ]
111  );
112  let mut i = 0;
113  for (a, b) in arr {
114    let mut x: String = String::new();
115    for _ in 0..(b.parse::<i64>().unwrap() * 100) / total {
116      x = x + "■";
117    }
118    table.add_row(
119      vec![
120        Cell::new(&(i+1).to_string()).fg(color_by_index(i % 6)),
121        Cell::new(&a).fg(color_by_index(i % 6)),
122        Cell::new(&b).fg(color_by_index(i % 6)),
123        Cell::new(&x).fg(color_by_index(i % 6))
124      ]
125    );
126    i = i + 1;
127  }
128  return table;
129}
130fn color_by_index(index: usize) -> Color {
131  match index {
132    0 => Color::Red,
133    1 => Color::Green,
134    2 => Color::Yellow,
135    3 => Color::Blue,
136    4 => Color::Magenta,
137    5 => Color::Cyan,
138    6 => Color::White,
139    _ => Color::Reset,
140  }
141}
142
143fn year_to_unix(year: i32) -> (i64, i64) {
144  let start = Utc.with_ymd_and_hms(year, 1, 1, 00, 00, 00).unwrap().timestamp();
145  let end = Utc.with_ymd_and_hms(year, 12, 31, 23, 59, 59).unwrap().timestamp();
146  return (start * 1000, end * 1000);
147}
148
149// pub fn rand_string(len: usize, chars: &str) -> String {
150//   let mut rng = rand::thread_rng();
151//   let mut s = String::with_capacity(len);
152//   for _ in 0..len {
153//     let idx = rng.gen_range(0..chars.len());
154//     s.push(chars.chars().nth(idx).unwrap());
155//   }
156//   return s;
157// }
158
159// pub fn fill_data(conn: &Connection) -> Result<()> {
160//   let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
161//   for _ in 0..100_000 {
162//     let command = rand_string(3, chars);
163//     let hostname = rand_string(4, chars);
164//     let time: String = rand
165//       ::thread_rng()
166//       .gen_range(1671512400..1704949200)
167//       .to_string();
168//     conn.execute(
169//       "insert into history (command_line, start_timestamp, hostname) values (?1, ?2, ?3)",
170//       [&command, &time, &hostname]
171//     )?;
172//   }
173//   return Ok(());
174// } // Haha this is just for side effects