Initial upload: GitPulse - Developer Productivity Analyzer CLI tool
This commit is contained in:
102
src/utils/author.rs
Normal file
102
src/utils/author.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
use crate::models::AuthorStats;
|
||||
use chrono::{DateTime, Utc};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn normalize_author_identity(name: &str, email: &str) -> String {
|
||||
let normalized_email = email.to_lowercase();
|
||||
let normalized_name = name.trim();
|
||||
|
||||
let (first, last) = {
|
||||
let parts: Vec<&str> = normalized_name.split_whitespace().collect();
|
||||
if parts.len() >= 2 {
|
||||
(parts[0], parts[parts.len() - 1])
|
||||
} else {
|
||||
(normalized_name, "")
|
||||
}
|
||||
};
|
||||
|
||||
format!("{} <{}>", first, normalized_email)
|
||||
}
|
||||
|
||||
pub struct AuthorAggregator {
|
||||
pub stats: HashMap<String, AuthorStats>,
|
||||
}
|
||||
|
||||
impl AuthorAggregator {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
stats: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_commit(
|
||||
&mut self,
|
||||
name: &str,
|
||||
email: &str,
|
||||
timestamp: DateTime<Utc>,
|
||||
changes: Option<(usize, usize, usize)>,
|
||||
) {
|
||||
let identity = normalize_author_identity(name, email);
|
||||
|
||||
let entry = self.stats.entry(identity.clone()).or_insert_with(|| AuthorStats {
|
||||
name: {
|
||||
let parts: Vec<&str> = name.split_whitespace().collect();
|
||||
parts.first().unwrap_or(&name).to_string()
|
||||
},
|
||||
email: email.to_string(),
|
||||
commits: 0,
|
||||
lines_added: 0,
|
||||
lines_removed: 0,
|
||||
net_change: 0,
|
||||
files_changed: 0,
|
||||
first_commit: timestamp.to_rfc3339(),
|
||||
last_commit: timestamp.to_rfc3339(),
|
||||
active_days: 0,
|
||||
average_commits_per_day: 0.0,
|
||||
busiest_day: String::new(),
|
||||
busiest_day_count: 0,
|
||||
commit_messages: Vec::new(),
|
||||
});
|
||||
|
||||
entry.commits += 1;
|
||||
|
||||
if let Some((added, removed, files)) = changes {
|
||||
entry.lines_added += added;
|
||||
entry.lines_removed += removed;
|
||||
entry.net_change = entry.lines_added as i64 - entry.lines_removed as i64;
|
||||
entry.files_changed += files;
|
||||
}
|
||||
|
||||
if timestamp.to_rfc3339() < entry.first_commit {
|
||||
entry.first_commit = timestamp.to_rfc3339();
|
||||
}
|
||||
if timestamp.to_rfc3339() > entry.last_commit {
|
||||
entry.last_commit = timestamp.to_rfc3339();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finalize(&mut self) {
|
||||
for stats in self.stats.values_mut() {
|
||||
let first = DateTime::parse_from_rfc3339(&stats.first_commit)
|
||||
.unwrap_or_else(|_| DateTime::from_timestamp(0, 0).unwrap().into());
|
||||
let last = DateTime::parse_from_rfc3339(&stats.last_commit)
|
||||
.unwrap_or_else(|_| DateTime::from_timestamp(0, 0).unwrap().into());
|
||||
|
||||
let days = (last - first).num_days() as f64;
|
||||
stats.average_commits_per_day = if days > 0.0 {
|
||||
stats.commits as f64 / days
|
||||
} else {
|
||||
stats.commits as f64
|
||||
};
|
||||
}
|
||||
|
||||
let mut authors: Vec<_> = self.stats.values_mut().collect();
|
||||
authors.sort_by(|a, b| b.commits.cmp(&a.commits));
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AuthorAggregator {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user