use std::collections::HashMap;
use crate::models::HistoryEntry;
pub fn count_commands(entries: &[HistoryEntry]) -> HashMap<String, usize> {
count_commands_at_depth(entries, 1)
}
pub fn count_commands_at_depth(entries: &[HistoryEntry], depth: usize) -> HashMap<String, usize> {
let mut counts = HashMap::new();
if depth == 0 {
return counts;
}
if depth == 1 {
for entry in entries {
let key = entry.command.trim().to_lowercase();
if key.is_empty() {
continue;
}
*counts.entry(key).or_insert(0) += 1;
}
return counts;
}
for entry in entries {
let mut tokens = entry.raw.split_whitespace().take(depth);
let key = tokens.next();
let key = match key {
Some(first) => {
let mut s = String::from(first);
for t in tokens {
s.push(' ');
s.push_str(t);
}
s.to_lowercase()
}
None => continue,
};
if key.is_empty() {
continue;
}
*counts.entry(key).or_insert(0) += 1;
}
counts
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn counts_basic_commands() {
let entries = vec![
HistoryEntry::new("ls", "ls"),
HistoryEntry::new("ls -la", "ls"),
HistoryEntry::new("git status", "git"),
];
let counts = count_commands(&entries);
assert_eq!(counts["ls"], 2);
assert_eq!(counts["git"], 1);
}
#[test]
fn normalizes_to_lowercase() {
let entries = vec![
HistoryEntry::new("Git status", "Git"),
HistoryEntry::new("git commit", "git"),
HistoryEntry::new("GIT push", "GIT"),
];
let counts = count_commands(&entries);
assert_eq!(counts["git"], 3);
}
#[test]
fn trims_spaces_before_counting() {
let entries = vec![
HistoryEntry::new(" ls ", " ls "),
HistoryEntry::new("ls", "ls"),
];
let counts = count_commands(&entries);
assert_eq!(counts["ls"], 2);
}
#[test]
fn skips_empty_commands() {
let entries = vec![
HistoryEntry::new("", ""),
HistoryEntry::new(" ", " "),
HistoryEntry::new("ls", "ls"),
];
let counts = count_commands(&entries);
assert_eq!(counts.len(), 1);
assert_eq!(counts["ls"], 1);
}
#[test]
fn empty_entries_returns_empty_map() {
let entries: Vec<HistoryEntry> = vec![];
let counts = count_commands(&entries);
assert!(counts.is_empty());
}
#[test]
fn depth_one_matches_count_commands() {
let entries = vec![
HistoryEntry::new("ls -la", "ls"),
HistoryEntry::new("git status", "git"),
HistoryEntry::new("ls", "ls"),
];
let d1 = count_commands_at_depth(&entries, 1);
assert_eq!(d1["ls"], 2);
assert_eq!(d1["git"], 1);
}
#[test]
fn depth_two_separates_subcommands() {
let entries = vec![
HistoryEntry::new("git commit", "git"),
HistoryEntry::new("git push", "git"),
HistoryEntry::new("git commit -m x", "git"),
HistoryEntry::new("ls", "ls"),
];
let d2 = count_commands_at_depth(&entries, 2);
assert_eq!(d2.get("git commit"), Some(&2));
assert_eq!(d2.get("git push"), Some(&1));
assert_eq!(d2.get("ls"), Some(&1));
}
#[test]
fn depth_zero_returns_empty() {
let entries = vec![HistoryEntry::new("ls", "ls")];
assert!(count_commands_at_depth(&entries, 0).is_empty());
}
#[test]
fn depth_exceeding_tokens_uses_whole_line() {
let entries = vec![HistoryEntry::new("git push", "git")];
let d5 = count_commands_at_depth(&entries, 5);
assert_eq!(d5.get("git push"), Some(&1));
}
}