1use regex::Regex;
6
7use crate::error::Result;
8use crate::logic::{aggregate, filter, render, sort};
9use crate::model::Options;
10use crate::repo::{self, Repo, WalkedCommit};
11
12pub fn run(repo: &Repo, opts: &Options) -> Result<String> {
20 let authors = filter::compile_authors(&opts.authors)?;
24 let since = repo::parse_date(opts.since.as_deref())?;
25 let until = repo::parse_date(opts.until.as_deref())?;
26
27 let walked = repo.walk(&opts.range, opts.reviews)?;
29
30 let mut output = String::new();
31
32 output.push_str(&stats_section(repo, opts, &authors, since, until, &walked)?);
33
34 if opts.reviews {
37 let reviews = aggregate::aggregate_reviews(walked.iter().map(|w| &w.meta), opts.email);
38 if !reviews.is_empty() {
39 if !output.is_empty() {
42 output.push('\n');
43 }
44 output.push_str(&render::render_reviews(&reviews));
45 output.push('\n');
46 }
47 }
48
49 Ok(output)
50}
51
52fn stats_section(
53 repo: &Repo,
54 opts: &Options,
55 authors: &[Regex],
56 since: Option<i64>,
57 until: Option<i64>,
58 walked: &[WalkedCommit],
59) -> Result<String> {
60 let kept_idx = filter::keep_indices(walked.iter().map(|w| &w.meta), authors, since, until);
62 let kept: Vec<&WalkedCommit> = kept_idx.iter().map(|&i| &walked[i]).collect();
63
64 let diffs = repo.numstats(&kept)?;
66
67 let rows: Vec<aggregate::CommitStat> = kept
69 .iter()
70 .zip(diffs)
71 .map(|(w, diff)| aggregate::CommitStat {
72 author_key: aggregate::author_key(&w.meta.author, opts.email),
73 diff,
74 })
75 .collect();
76 let mut stats = aggregate::aggregate(&rows);
77 if stats.is_empty() {
78 return Ok(String::new());
79 }
80 sort::sort_stats(&mut stats, opts.sort, opts.reverse);
81 stats.push(aggregate::compute_totals(&stats));
82
83 let mut section = render::render_stats(&stats);
84 section.push('\n');
85 Ok(section)
86}