use std::sync::atomic::{AtomicUsize, Ordering}; use git::bstr::{BStr, ByteSlice}; use git_repository as git; use itertools::Itertools; use crate::hours::core::HOURS_PER_WORKDAY; #[derive(Debug)] pub struct WorkByPerson { pub name: Vec<&'static BStr>, pub email: Vec<&'static BStr>, pub hours: f32, pub num_commits: u32, pub files: FileStats, pub lines: LineStats, } impl<'a> WorkByPerson { pub fn merge(&mut self, other: &'a WorkByEmail) { if !self.name.contains(&other.name) { self.name.push(other.name); } if !self.email.contains(&other.email) { self.email.push(other.email); } self.num_commits += other.num_commits; self.hours += other.hours; self.files.add(&other.files); self.lines.add(&other.lines); } } impl<'a> From<&'a WorkByEmail> for WorkByPerson { fn from(w: &'a WorkByEmail) -> Self { WorkByPerson { name: vec![w.name], email: vec![w.email], hours: w.hours, num_commits: w.num_commits, files: w.files, lines: w.lines, } } } impl WorkByPerson { pub fn write_to( &self, total_hours: f32, total_files: Option, total_lines: Option, mut out: impl std::io::Write, ) -> std::io::Result<()> { writeln!( out, "{} <{}>", self.name.iter().join(", "), self.email.iter().join(", ") )?; writeln!(out, "{} commits found", self.num_commits)?; writeln!( out, "total time spent: {:.02}h ({:.02} 8h days, {:.02}%)", self.hours, self.hours / HOURS_PER_WORKDAY, (self.hours / total_hours) * 100.0 )?; if let Some(total) = total_files { writeln!( out, "total files added/removed/modified: {}/{}/{} ({:.02}%)", self.files.added, self.files.removed, self.files.modified, (self.files.sum() / total.sum()) * 100.0 )?; } if let Some(total) = total_lines { writeln!( out, "total lines added/removed: {}/{} ({:.02}%)", self.lines.added, self.lines.removed, (self.lines.sum() / total.sum()) * 100.0 )?; } Ok(()) } } #[derive(Debug)] pub struct WorkByEmail { pub name: &'static BStr, pub email: &'static BStr, pub hours: f32, pub num_commits: u32, pub files: FileStats, pub lines: LineStats, } /// File statistics for a particular commit. #[derive(Debug, Default, Copy, Clone)] pub struct FileStats { /// amount of added files pub added: usize, /// amount of removed files pub removed: usize, /// amount of modified files pub modified: usize, } /// Line statistics for a particular commit. #[derive(Debug, Default, Copy, Clone)] pub struct LineStats { /// amount of added lines pub added: usize, /// amount of removed lines pub removed: usize, } impl FileStats { pub fn add(&mut self, other: &FileStats) -> &mut Self { self.added += other.added; self.removed += other.removed; self.modified += other.modified; self } pub fn added(&self, other: &FileStats) -> Self { let mut a = *self; a.add(other); a } pub fn sum(&self) -> f32 { (self.added + self.removed + self.modified) as f32 } } impl LineStats { pub fn add(&mut self, other: &LineStats) -> &mut Self { self.added += other.added; self.removed += other.removed; self } pub fn added(&self, other: &LineStats) -> Self { let mut a = *self; a.add(other); a } pub fn sum(&self) -> f32 { (self.added + self.removed) as f32 } } pub fn add_lines(line_stats: bool, lines_counter: Option<&AtomicUsize>, mut lines: &mut LineStats, id: git::Id<'_>) { if let Some(Ok(blob)) = line_stats.then(|| id.object()) { let nl = blob.data.lines_with_terminator().count(); lines.added += nl; if let Some(c) = lines_counter { c.fetch_add(nl, Ordering::SeqCst); } } } pub fn remove_lines(line_stats: bool, lines_counter: Option<&AtomicUsize>, mut lines: &mut LineStats, id: git::Id<'_>) { if let Some(Ok(blob)) = line_stats.then(|| id.object()) { let nl = blob.data.lines_with_terminator().count(); lines.removed += nl; if let Some(c) = lines_counter { c.fetch_add(nl, Ordering::SeqCst); } } }