use std::collections::HashMap;
use regex::Regex;
use crate::config::types::GroupConfig;
use super::extract::interpolate;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GroupCount {
pub label: String,
pub count: usize,
}
pub fn collect_groups(config: &GroupConfig, lines: &[&str]) -> Vec<GroupCount> {
let Ok(re) = Regex::new(&config.key.pattern) else {
return Vec::new();
};
let mut counts: HashMap<String, usize> = HashMap::new();
for line in lines {
if let Some(caps) = re.captures(line) {
let raw_key = interpolate(&config.key.output, &caps);
let label = config
.labels
.get(&raw_key)
.cloned()
.unwrap_or_else(|| raw_key.clone());
*counts.entry(label).or_insert(0) += 1;
}
}
let mut result: Vec<GroupCount> = counts
.into_iter()
.map(|(label, count)| GroupCount { label, count })
.collect();
result.sort_by(|a, b| a.label.cmp(&b.label));
result
}
pub fn render_group_counts(counts: &[GroupCount], format: &str, empty: Option<&str>) -> String {
if counts.is_empty() {
return empty.unwrap_or("").to_string();
}
counts
.iter()
.map(|gc| {
format
.replace("{label}", &gc.label)
.replace("{count}", &gc.count.to_string())
})
.collect::<Vec<_>>()
.join("\n")
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use crate::config::types::ExtractRule;
fn git_status_group_config() -> GroupConfig {
let mut labels = HashMap::new();
labels.insert("M ".to_string(), "modified".to_string());
labels.insert(" M".to_string(), "modified (unstaged)".to_string());
labels.insert("??".to_string(), "untracked".to_string());
labels.insert("A ".to_string(), "added".to_string());
labels.insert("D ".to_string(), "deleted".to_string());
GroupConfig {
key: ExtractRule {
pattern: r"^(.{2}) ".to_string(),
output: "{1}".to_string(),
},
labels,
}
}
#[test]
fn collect_groups_basic() {
let config = git_status_group_config();
let lines = vec![
"M src/main.rs",
" M src/lib.rs",
"?? new_file.txt",
"?? another.txt",
];
let groups = collect_groups(&config, &lines);
assert_eq!(groups.len(), 3);
assert_eq!(groups[0].label, "modified");
assert_eq!(groups[0].count, 1);
assert_eq!(groups[1].label, "modified (unstaged)");
assert_eq!(groups[1].count, 1);
assert_eq!(groups[2].label, "untracked");
assert_eq!(groups[2].count, 2);
}
#[test]
fn collect_groups_empty_lines() {
let config = git_status_group_config();
let groups = collect_groups(&config, &[]);
assert!(groups.is_empty());
}
#[test]
fn collect_groups_no_matches() {
let config = git_status_group_config();
let lines = vec!["x", "ab"];
let groups = collect_groups(&config, &lines);
assert!(groups.is_empty());
}
#[test]
fn collect_groups_invalid_regex() {
let config = GroupConfig {
key: ExtractRule {
pattern: "[invalid".to_string(),
output: "{1}".to_string(),
},
labels: HashMap::new(),
};
let lines = vec!["M src/main.rs"];
let groups = collect_groups(&config, &lines);
assert!(groups.is_empty());
}
#[test]
fn collect_groups_unknown_key_uses_raw() {
let config = GroupConfig {
key: ExtractRule {
pattern: r"^(.{2}) ".to_string(),
output: "{1}".to_string(),
},
labels: HashMap::new(),
};
let lines = vec!["M src/main.rs", "M src/lib.rs"];
let groups = collect_groups(&config, &lines);
assert_eq!(groups.len(), 1);
assert_eq!(groups[0].label, "M ");
assert_eq!(groups[0].count, 2);
}
#[test]
fn render_group_counts_basic() {
let counts = vec![
GroupCount {
label: "modified".to_string(),
count: 2,
},
GroupCount {
label: "untracked".to_string(),
count: 1,
},
];
let result = render_group_counts(&counts, " {label}: {count}", None);
assert_eq!(result, " modified: 2\n untracked: 1");
}
#[test]
fn render_group_counts_empty_with_message() {
let result = render_group_counts(
&[],
" {label}: {count}",
Some("clean \u{2014} nothing to commit"),
);
assert_eq!(result, "clean \u{2014} nothing to commit");
}
#[test]
fn render_group_counts_empty_no_message() {
let result = render_group_counts(&[], " {label}: {count}", None);
assert_eq!(result, "");
}
#[test]
fn render_group_counts_single() {
let counts = vec![GroupCount {
label: "added".to_string(),
count: 5,
}];
let result = render_group_counts(&counts, "{label} ({count})", None);
assert_eq!(result, "added (5)");
}
}