From a4a61fac82174184fe2f974837ff340146cd585d Mon Sep 17 00:00:00 2001 From: nathan dawit Date: Tue, 14 Mar 2023 15:01:42 +0300 Subject: [PATCH] refactor: rethink how the bar arg works --- src/args.rs | 68 +++++++++++++++++++++++++---------- src/display.rs | 82 ++++++++++++++++++++++++++++++++++++++++++ src/hist_file.rs | 9 ++--- src/main.rs | 5 ++- src/utils.rs | 92 ------------------------------------------------ 5 files changed, 136 insertions(+), 120 deletions(-) create mode 100644 src/display.rs delete mode 100644 src/utils.rs diff --git a/src/args.rs b/src/args.rs index afa66e6..20193fa 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,31 +1,23 @@ +use std::str::FromStr; + +// use utf8_slice; + use clap::Parser; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] pub struct Args { /// Display top n commands - #[arg(short, long)] - pub count: Option, - - /// Make output pretty - #[arg(short, long, default_value_t = true)] - pub pretty: bool, + #[arg(short, long, default_value_t = 10)] + pub count: usize, /// Show debug messages #[arg(long, default_value_t = false)] pub debug: bool, - /// Bar opening character - #[arg(long, default_value_t = '[')] - pub bar_open: char, - - /// Bar closing character - #[arg(long, default_value_t = ']')] - pub bar_close: char, - - /// Bar character - #[arg(long, default_value_t = '▮')] - pub bar: char, + /// Change how the bar looks --bar [,▮, ,] + #[arg(long, default_value_t = Default::default())] + pub bar: Bar, /// Regular expression to allow for the removal of prefixes in shells like zsh. Default value is for zsh. NOTE: shell overrides this argument #[arg(short, long, default_value_t = String::from(""))] @@ -34,5 +26,45 @@ pub struct Args { /// Preset regular expressions for common shells: Bash, ZSH, Fish. #[arg(long, default_value_t = String::from(""))] pub shell: String, - +} + +#[derive(Debug, Clone)] +pub struct Bar { + pub opening: String, + pub closing: String, + pub fill: String, + pub empty: String, +} +impl Default for Bar { + fn default() -> Self { + Bar { + opening: "[".to_owned(), + fill: "▮".to_owned(), + empty: " ".to_owned(), + closing: "]".to_owned(), + } + } +} + +impl FromStr for Bar { + type Err = String; + + fn from_str(s: &str) -> Result { + let chars = s.split(',').collect::>(); + match chars.len() { + 4 => Ok(Bar { + opening: chars.get(0).unwrap().to_string(), + fill: chars.get(1).unwrap().to_string(), + empty: chars.get(2).unwrap().to_string(), + closing: chars.get(3).unwrap().to_string(), + }), + _ => Err("Invalid bar length".to_string()), + } + } +} + +impl std::fmt::Display for Bar { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{},{},{},{}", self.opening, self.fill, self.empty, self.closing) + } } diff --git a/src/display.rs b/src/display.rs new file mode 100644 index 0000000..334740a --- /dev/null +++ b/src/display.rs @@ -0,0 +1,82 @@ +use std::{ + collections::{BTreeMap, HashMap}, + usize, +}; +use utf8_slice::slice; + +use crate::{hist_file::CommandMap, Args}; +use aecir::style::{Color, ColorName, Format}; + +type VeryComplexType = (String, Option, HashMap); +pub fn print(data: CommandMap, args: Args) { + let tree: BTreeMap = data + .into_iter() + .map(|(s, (f, o, h))| (f, (s, o, h))) + .collect(); + + let total = tree.len(); + let max = *tree.last_key_value().unwrap().0; + + let limited_tree: Vec<(usize, VeryComplexType)> = tree.into_iter().rev().collect(); + + for (freq, elem) in limited_tree[..(usize::min(args.count, limited_tree.len()))].iter() { + let (s, _o, h) = elem; + let mut sub_commands = h.iter().collect::>(); + sub_commands.sort_by(|a, b| b.1.cmp(a.1)); + let sub_commands = sub_commands[..(usize::min(3, sub_commands.len()))] + .iter() + .map(|x| x.0.to_owned()) + .collect(); + + print_command(s, *freq, max, total, &args, Some(sub_commands)); + } +} + + +pub fn print_command( + command: &str, + invocations: usize, + max: usize, + total: usize, + args: &Args, + sub_commands: Option>, +) { + let percentage = invocations as f32 / total as f32; + let num_of_bars = ((invocations as f32 / max as f32) * 10.) as usize ; + let bars = args.bar.fill.repeat(num_of_bars); + let empties = args.bar.empty.repeat(10 - num_of_bars); + let bar: String = format!( + "{}{}", + bars, empties + ); + let pretty_sub_commands = if let Some(sub_commands) = sub_commands { + let trim_len = sub_commands.len().min(3); + let mut x = sub_commands[..trim_len].join(", "); + x.push_str(" ..."); + x + } else { + "".to_string() + }; + + let opening_char = &args.bar.opening; + let bar_first = slice(&bar, 0, 2); + let bar_second = slice(&bar, 2, 5); + let bar_third = slice(&bar, 5, 10); + let closing_char = &args.bar.closing; + + let reset_style = aecir::style::reset_all(); + + let (red, yellow, green, gray, bold) = ( + Color::Fg(ColorName::Red).to_string(), + Color::Fg(ColorName::Yellow).to_string(), + Color::Fg(ColorName::Green).to_string(), + Color::Fg(ColorName::LightBlack).to_string(), + Format::Bold.to_string(), + ); + + println!( + "{opening_char}{red}{bar_first: <2}{yellow}{bar_second: <3}{green}{bar_third: <5}{reset_style}{closing_char} \ + {percentage: >5.2}% {gray}{invocations:5}{reset_style}\ + {bold} {command} {reset_style}{gray}{pretty_sub_commands} {reset_style}", + ); +} diff --git a/src/hist_file.rs b/src/hist_file.rs index c2eb934..acd98ec 100644 --- a/src/hist_file.rs +++ b/src/hist_file.rs @@ -14,7 +14,6 @@ fn print_warning(warning: &str) { } pub fn get_contents(hist_file: std::fs::File, args: &Args) -> String { - let reader = BufReader::new(hist_file); let mut contents = String::new(); @@ -69,11 +68,7 @@ pub fn parse_contents(contents: String, args: &Args) -> Vec { let reg = Regex::new("('(?:.|[^'\n])*'|\"(?:.|[^\"\n])*\")").unwrap(); let unquoted_lines = shell_parsed.map(|line| reg.replace_all(line, "").to_string()); - let command_lines = unquoted_lines - .flat_map(get_commands) - .collect(); - - command_lines + unquoted_lines.flat_map(get_commands).collect() } pub(crate) type CommandMap = HashMap, HashMap)>; @@ -104,7 +99,7 @@ pub fn process_lines(lines: Vec, _args: &Args) -> CommandMap { if leaders.contains(&first.as_str()) { parent_entry.1 = Some(true); output - .entry(( *second ).to_string()) + .entry((*second).to_string()) .or_insert((0, None, HashMap::new())) .0 += 1; } else if super_commands.contains(&first.as_str()) { diff --git a/src/main.rs b/src/main.rs index dfaecca..6cff636 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ mod hist_file; -mod utils; +mod display; mod args; use std::env; @@ -17,6 +17,5 @@ fn main() { let command_lines = hist_file::parse_contents(contents, &args); let commands = hist_file::process_lines(command_lines, &args); - - utils::display_sorted(commands, args); + display::print(commands, args); } diff --git a/src/utils.rs b/src/utils.rs deleted file mode 100644 index 602d458..0000000 --- a/src/utils.rs +++ /dev/null @@ -1,92 +0,0 @@ -use std::{ - collections::{BTreeMap, HashMap}, - usize, -}; -use utf8_slice::slice; - -use crate::{hist_file::CommandMap, Args}; -use aecir::style::{Color, ColorName, Format}; - -type VeryComplexType = (String, Option, HashMap); -pub fn display_sorted(data: CommandMap, args: Args) { - let tree: BTreeMap = data - .into_iter() - .map(|(s, (f, o, h))| (f, (s, o, h))) - .collect(); - - let total = tree.len(); - let max = *tree.last_key_value().unwrap().0; - - let limited_tree: Vec<(usize, VeryComplexType)> = - tree.into_iter().rev().collect(); - - for (freq, elem) in - limited_tree[..(usize::min(args.count.unwrap(), limited_tree.len()))].iter() - { - let (s, _o, h) = elem; - let mut sub_commands = h.iter().collect::>(); - sub_commands.sort_by(|a, b| b.1.cmp(a.1)); - let sub_commands = sub_commands[..(usize::min(3, sub_commands.len()))] - .iter() - .map(|x| x.0.to_owned()) - .collect(); - - print_command( - s, - *freq, - *freq as f32 / total as f32, - max, - &args, - Some(sub_commands), - ); - } -} - -pub fn print_command( - command: &str, - invocations: usize, - percentage: f32, - max: usize, - args: &Args, - sub_commands: Option>, -) { - let bar: String = format!( - "{: <10}", - args.bar - .to_string() - .repeat(((invocations as f32 / max as f32) * 10.) as usize) - ); - let pretty_sub_commands = if let Some(sub_commands) = sub_commands { - let trim_len = sub_commands.len().min(3); - let mut x = sub_commands[..trim_len].join(", "); - x.push_str(" ..."); - x - } else { - "".to_string() - }; - if args.pretty { - println!( - "{opening_char}{red}{bar_first: <2}{yellow}{bar_second: <3}{green}{bar_third: <5}{reset}{closing_char} \ - {percentage: >5.2}% {gray}{invocations:5}{reset}\ - {bold} {command} {reset_style}{gray}{pretty_sub_commands} {reset}", - opening_char = args.bar_open, - red = Color::Fg(ColorName::Red), - bar_first = slice(&bar, 0, 2), - yellow = Color::Fg(ColorName::Yellow), - bar_second = slice(&bar, 2, 5), - green = Color::Fg(ColorName::Green), - bar_third = slice(&bar, 5, 10), - reset = aecir::style::reset_colors(), - closing_char = args.bar_close, - gray = Color::Fg(ColorName::LightBlack), - bold = Format::Bold, - reset_style = aecir::style::reset_all(), - ); - } else { - println!( - "{opening_char}{bar}{closing_char} {percentage: >5.2}% {invocations:5} {command}", - opening_char = args.bar_open, - closing_char = args.bar_close, - ); - } -}