feat: allow configuring stats prefix (#1411)

This commit is contained in:
Ellie Huxtable 2023-11-23 09:48:43 +00:00 committed by GitHub
parent fbaa245439
commit 0c9d7367c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 50 additions and 14 deletions

View File

@ -143,6 +143,24 @@ pub enum WordJumpMode {
Subl, Subl,
} }
#[derive(Clone, Debug, Deserialize)]
pub struct Stats {
pub common_prefix: Vec<String>, // sudo, etc. commands we want to strip off
pub common_subcommands: Vec<String>, // kubectl, commands we should consider subcommands for
}
impl Default for Stats {
fn default() -> Self {
Self {
common_prefix: vec!["sudo", "doas"].into_iter().map(String::from).collect(),
common_subcommands: vec!["cargo", "go", "git", "npm", "yarn", "pnpm", "kubectl"]
.into_iter()
.map(String::from)
.collect(),
}
}
}
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub struct Settings { pub struct Settings {
pub dialect: Dialect, pub dialect: Dialect,
@ -169,10 +187,13 @@ pub struct Settings {
pub word_jump_mode: WordJumpMode, pub word_jump_mode: WordJumpMode,
pub word_chars: String, pub word_chars: String,
pub scroll_context_lines: usize, pub scroll_context_lines: usize,
#[serde(with = "serde_regex", default = "RegexSet::empty")] #[serde(with = "serde_regex", default = "RegexSet::empty")]
pub history_filter: RegexSet, pub history_filter: RegexSet,
#[serde(with = "serde_regex", default = "RegexSet::empty")] #[serde(with = "serde_regex", default = "RegexSet::empty")]
pub cwd_filter: RegexSet, pub cwd_filter: RegexSet,
pub secrets_filter: bool, pub secrets_filter: bool,
pub workspaces: bool, pub workspaces: bool,
pub ctrl_n_shortcuts: bool, pub ctrl_n_shortcuts: bool,
@ -181,6 +202,9 @@ pub struct Settings {
pub network_timeout: u64, pub network_timeout: u64,
pub enter_accept: bool, pub enter_accept: bool,
#[serde(default)]
pub stats: Stats,
// This is automatically loaded when settings is created. Do not set in // This is automatically loaded when settings is created. Do not set in
// config! Keep secrets and settings apart. // config! Keep secrets and settings apart.
pub session_token: String, pub session_token: String,

View File

@ -23,14 +23,16 @@ pub struct Cmd {
count: usize, count: usize,
} }
fn compute_stats(history: &[History], count: usize) -> Result<()> { fn compute_stats(settings: &Settings, history: &[History], count: usize) -> Result<()> {
let mut commands = HashSet::<&str>::with_capacity(history.len()); let mut commands = HashSet::<&str>::with_capacity(history.len());
let mut prefixes = HashMap::<&str, usize>::with_capacity(history.len()); let mut prefixes = HashMap::<&str, usize>::with_capacity(history.len());
for i in history { for i in history {
// just in case it somehow has a leading tab or space or something (legacy atuin didn't ignore space prefixes) // just in case it somehow has a leading tab or space or something (legacy atuin didn't ignore space prefixes)
let command = i.command.trim(); let command = i.command.trim();
commands.insert(command); commands.insert(command);
*prefixes.entry(interesting_command(command)).or_default() += 1; *prefixes
.entry(interesting_command(settings, command))
.or_default() += 1;
} }
let unique = commands.len(); let unique = commands.len();
@ -109,15 +111,11 @@ impl Cmd {
let end = start + Duration::days(1); let end = start + Duration::days(1);
db.range(start, end).await? db.range(start, end).await?
}; };
compute_stats(&history, self.count)?; compute_stats(settings, &history, self.count)?;
Ok(()) Ok(())
} }
} }
// TODO: make this configurable?
static COMMON_COMMAND_PREFIX: &[&str] = &["sudo"];
static COMMON_SUBCOMMAND_PREFIX: &[&str] = &["cargo", "go", "git", "npm", "yarn", "pnpm"];
fn first_non_whitespace(s: &str) -> Option<usize> { fn first_non_whitespace(s: &str) -> Option<usize> {
s.char_indices() s.char_indices()
// find the first non whitespace char // find the first non whitespace char
@ -134,7 +132,7 @@ fn first_whitespace(s: &str) -> usize {
.map_or(s.len(), |(i, _)| i) .map_or(s.len(), |(i, _)| i)
} }
fn interesting_command(mut command: &str) -> &str { fn interesting_command<'a>(settings: &Settings, mut command: &'a str) -> &'a str {
// compute command prefix // compute command prefix
// we loop here because we might be working with a common command prefix (eg sudo) that we want to trim off // we loop here because we might be working with a common command prefix (eg sudo) that we want to trim off
let (i, prefix) = loop { let (i, prefix) = loop {
@ -142,7 +140,7 @@ fn interesting_command(mut command: &str) -> &str {
let prefix = &command[..i]; let prefix = &command[..i];
// is it a common prefix // is it a common prefix
if COMMON_COMMAND_PREFIX.contains(&prefix) { if settings.stats.common_prefix.contains(&String::from(prefix)) {
command = command[i..].trim_start(); command = command[i..].trim_start();
if command.is_empty() { if command.is_empty() {
// no commands following, just use the prefix // no commands following, just use the prefix
@ -164,7 +162,14 @@ fn interesting_command(mut command: &str) -> &str {
match subcommand_indices { match subcommand_indices {
// if there is a subcommand and it's a common one, then count the full prefix + subcommand // if there is a subcommand and it's a common one, then count the full prefix + subcommand
Some(end) if COMMON_SUBCOMMAND_PREFIX.contains(&prefix) => &command[..end], Some(end)
if settings
.stats
.common_subcommands
.contains(&String::from(prefix)) =>
{
&command[..end]
}
// otherwise just count the main command // otherwise just count the main command
_ => prefix, _ => prefix,
} }
@ -172,16 +177,23 @@ fn interesting_command(mut command: &str) -> &str {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use atuin_client::settings::Settings;
use super::interesting_command; use super::interesting_command;
#[test] #[test]
fn interesting_commands() { fn interesting_commands() {
assert_eq!(interesting_command("cargo"), "cargo"); let settings = Settings::default();
assert_eq!(interesting_command("cargo build foo bar"), "cargo build");
assert_eq!(interesting_command(&settings, "cargo"), "cargo");
assert_eq!( assert_eq!(
interesting_command("sudo cargo build foo bar"), interesting_command(&settings, "cargo build foo bar"),
"cargo build" "cargo build"
); );
assert_eq!(interesting_command("sudo"), "sudo"); assert_eq!(
interesting_command(&settings, "sudo cargo build foo bar"),
"cargo build"
);
assert_eq!(interesting_command(&settings, "sudo"), "sudo");
} }
} }