Skip to content

Commit 4a25c2f

Browse files
committed
feat: enhance file scanning and contributor statistics
- Added regex filtering for file paths based on user-defined patterns. - Improved directory scanning to exclude the '.git' directory. - Enhanced contributor statistics display with an impact percentage based on commits, insertions, deletions, and files changed.
1 parent 76d4270 commit 4a25c2f

4 files changed

Lines changed: 96 additions & 1 deletion

File tree

src/main.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,22 @@ fn main() -> Result<()> {
305305
file_stats.extend(scanner::scan_directory(dir, args.exclude.as_ref(), ext_filter.as_ref(), args.cache));
306306
}
307307

308+
if let Some(regexes) = &args.regex {
309+
let compiled_regexes: Vec<regex::Regex> = regexes.iter()
310+
.filter_map(|r| regex::Regex::new(r).ok())
311+
.collect();
312+
if !compiled_regexes.is_empty() {
313+
file_stats.retain(|s| {
314+
if let Some(p) = s.path.to_str() {
315+
let p_normalized = p.replace('\\', "/");
316+
compiled_regexes.iter().any(|re| re.is_match(&p_normalized))
317+
} else {
318+
false
319+
}
320+
});
321+
}
322+
}
323+
308324
if let Some(min_lines) = args.min_lines {
309325
file_stats.retain(|s| s.lines >= min_lines);
310326
}
@@ -432,6 +448,44 @@ fn main() -> Result<()> {
432448
unified_stats = Some(u_stats);
433449
}
434450

451+
let has_specific_view = args.details
452+
|| args.top.is_some()
453+
|| args.failures_only
454+
|| args.health
455+
|| args.complexity_graph
456+
|| args.langdist
457+
|| args.apidoc
458+
|| args.typestats
459+
|| args.refactor_map
460+
|| args.refactor_suggest
461+
|| args.autofix_suggest
462+
|| args.open.is_some()
463+
|| args.structure_mermaid
464+
|| args.complexitymap
465+
|| args.report_issues
466+
|| args.badge_sustainability
467+
|| args.lang_card_svg
468+
|| args.badges
469+
|| args.readme
470+
|| args.hotspot
471+
|| args.authors
472+
|| args.contributors
473+
|| args.contributors_detail
474+
|| args.churn
475+
|| args.blame.is_some()
476+
|| args.trend
477+
|| args.find.is_some()
478+
|| args.tree
479+
|| args.dup
480+
|| args.list_extensions
481+
|| args.maxmin
482+
|| args.warnsize
483+
|| args.naming
484+
|| args.deadcode
485+
|| args.style_check
486+
|| args.openapi
487+
|| args.test_coverage.is_some();
488+
435489
if args.md {
436490
let md = visualizer::generate_markdown(&aggregated, unified_stats.as_deref());
437491
visualizer::save_or_print(&md, args.output.as_ref());
@@ -446,7 +500,9 @@ fn main() -> Result<()> {
446500
visualizer::save_or_print(&json, args.output.as_ref());
447501
} else {
448502
// Normal text output
449-
visualizer::print_summary_table(&aggregated);
503+
if !has_specific_view || args.summary {
504+
visualizer::print_summary_table(&aggregated);
505+
}
450506

451507
if let Some(ref details) = unified_stats {
452508
if args.details || args.top.is_some() || args.failures_only || args.size || args.file_age {

src/scanner.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,13 @@ pub fn scan_directory(dir: &str, excludes: Option<&Vec<String>>, exts: Option<&V
3939
let exc_list = exc_list.clone();
4040
builder.filter_entry(move |e| {
4141
let file_name = e.file_name().to_string_lossy();
42+
if file_name == ".git" {
43+
return false;
44+
}
4245
!exc_list.iter().any(|exc| file_name == *exc)
4346
});
47+
} else {
48+
builder.filter_entry(|e| e.file_name().to_string_lossy() != ".git");
4449
}
4550

4651
let walker = builder.build();

src/search.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub fn print_tree(dir: &str) {
1010
println!("Project Tree for '{}':", dir);
1111
let mut builder = WalkBuilder::new(dir);
1212
builder.hidden(false).ignore(true).git_ignore(true);
13+
builder.filter_entry(|e| e.file_name().to_string_lossy() != ".git");
1314
let walker = builder.build();
1415

1516
let root_depth = Path::new(dir).components().count();
@@ -54,8 +55,13 @@ pub fn find_pattern(dir: &str, pattern: &str, excludes: Option<&Vec<String>>, ex
5455
let exc_list = exc_list.clone();
5556
builder.filter_entry(move |e| {
5657
let file_name = e.file_name().to_string_lossy();
58+
if file_name == ".git" {
59+
return false;
60+
}
5761
!exc_list.iter().any(|exc| file_name == *exc)
5862
});
63+
} else {
64+
builder.filter_entry(|e| e.file_name().to_string_lossy() != ".git");
5965
}
6066

6167
let walker = builder.build();
@@ -127,8 +133,13 @@ pub fn detect_duplicates(dir: &str, excludes: Option<&Vec<String>>, exts: Option
127133
let exc_list = exc_list.clone();
128134
builder.filter_entry(move |e| {
129135
let file_name = e.file_name().to_string_lossy();
136+
if file_name == ".git" {
137+
return false;
138+
}
130139
!exc_list.iter().any(|exc| file_name == *exc)
131140
});
141+
} else {
142+
builder.filter_entry(|e| e.file_name().to_string_lossy() != ".git");
132143
}
133144

134145
let walker = builder.build();

src/visualizer.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ pub fn print_contributors_detail(stats: &[crate::git::ContributorDetail]) {
332332
let mut table = Table::new();
333333
table.set_header(vec![
334334
Cell::new("Contributor").add_attribute(Attribute::Bold).fg(Color::Cyan),
335+
Cell::new("Impact %").add_attribute(Attribute::Bold).fg(Color::Magenta),
335336
Cell::new("Commits").add_attribute(Attribute::Bold).fg(Color::Green),
336337
Cell::new("Insertions").add_attribute(Attribute::Bold).fg(Color::Green),
337338
Cell::new("Deletions").add_attribute(Attribute::Bold).fg(Color::Red),
@@ -342,9 +343,29 @@ pub fn print_contributors_detail(stats: &[crate::git::ContributorDetail]) {
342343
let mut total_insertions = 0;
343344
let mut total_deletions = 0;
344345

346+
let mut total_score = 0.0;
347+
let mut scores = Vec::new();
345348
for s in stats {
349+
// Heuristic: Commits carry high weight, files changed show breadth, insertions/deletions show volume.
350+
let score = (s.commits as f64 * 20.0)
351+
+ (s.insertions as f64 * 1.0)
352+
+ (s.deletions as f64 * 0.5)
353+
+ (s.files_changed.len() as f64 * 5.0);
354+
scores.push(score);
355+
total_score += score;
356+
}
357+
358+
// Sort stats by score (descending), we need to clone them or work with indices
359+
let mut indices: Vec<usize> = (0..stats.len()).collect();
360+
indices.sort_by(|&a, &b| scores[b].partial_cmp(&scores[a]).unwrap_or(std::cmp::Ordering::Equal));
361+
362+
for &i in &indices {
363+
let s = &stats[i];
364+
let pct = if total_score > 0.0 { (scores[i] / total_score) * 100.0 } else { 0.0 };
365+
346366
table.add_row(vec![
347367
Cell::new(&s.name),
368+
Cell::new(format!("{:.1}%", pct)).set_alignment(CellAlignment::Right).fg(Color::Magenta),
348369
Cell::new(s.commits).set_alignment(CellAlignment::Right),
349370
Cell::new(s.insertions).set_alignment(CellAlignment::Right),
350371
Cell::new(s.deletions).set_alignment(CellAlignment::Right),
@@ -357,13 +378,15 @@ pub fn print_contributors_detail(stats: &[crate::git::ContributorDetail]) {
357378

358379
table.add_row(vec![
359380
Cell::new("Total").add_attribute(Attribute::Bold).fg(Color::Yellow),
381+
Cell::new("100.0%").add_attribute(Attribute::Bold).set_alignment(CellAlignment::Right).fg(Color::Yellow),
360382
Cell::new(total_commits).add_attribute(Attribute::Bold).set_alignment(CellAlignment::Right).fg(Color::Yellow),
361383
Cell::new(total_insertions).add_attribute(Attribute::Bold).set_alignment(CellAlignment::Right).fg(Color::Yellow),
362384
Cell::new(total_deletions).add_attribute(Attribute::Bold).set_alignment(CellAlignment::Right).fg(Color::Yellow),
363385
Cell::new("-").set_alignment(CellAlignment::Right).fg(Color::Yellow),
364386
]);
365387

366388
println!("\n=== Detailed Contributor Statistics ===");
389+
println!("* Impact % is a heuristic combining commits, insertions, deletions, and files changed.");
367390
println!("{table}");
368391
}
369392

0 commit comments

Comments
 (0)