gitoxide_core/hours/
util.rs1use std::sync::atomic::{AtomicUsize, Ordering};
2
3use gix::bstr::{BStr, ByteSlice};
4
5use crate::hours::core::HOURS_PER_WORKDAY;
6
7#[derive(Debug)]
8pub struct WorkByPerson {
9 pub name: Vec<&'static BStr>,
10 pub email: Vec<&'static BStr>,
11 pub hours: f32,
12 pub num_commits: u32,
13 pub files: FileStats,
14 pub lines: LineStats,
15}
16
17impl WorkByPerson {
18 pub fn merge(&mut self, other: &WorkByEmail) {
19 if !self.name.contains(&other.name) {
20 self.name.push(other.name);
21 }
22 if !self.email.contains(&other.email) {
23 self.email.push(other.email);
24 }
25 self.num_commits += other.num_commits;
26 self.hours += other.hours;
27 self.files.add(&other.files);
28 self.lines.add(&other.lines);
29 }
30}
31
32impl<'a> From<&'a WorkByEmail> for WorkByPerson {
33 fn from(w: &'a WorkByEmail) -> Self {
34 WorkByPerson {
35 name: vec![w.name],
36 email: vec![w.email],
37 hours: w.hours,
38 num_commits: w.num_commits,
39 files: w.files,
40 lines: w.lines,
41 }
42 }
43}
44
45fn join<I>(mut iter: I, sep: &str) -> String
52where
53 I: Iterator,
54 <I as Iterator>::Item: std::fmt::Display,
55{
56 use ::std::fmt::Write;
57
58 match iter.next() {
59 None => String::new(),
60 Some(first_elt) => {
61 let (lower, _) = iter.size_hint();
63 let mut result = String::with_capacity(sep.len() * lower);
64 write!(&mut result, "{first_elt}").expect("enough memory");
65 iter.for_each(|elt| {
66 result.push_str(sep);
67 write!(&mut result, "{elt}").expect("enough memory");
68 });
69 result
70 }
71 }
72}
73
74impl WorkByPerson {
75 pub fn write_to(
76 &self,
77 total_hours: f32,
78 total_files: Option<FileStats>,
79 total_lines: Option<LineStats>,
80 mut out: impl std::io::Write,
81 ) -> std::io::Result<()> {
82 writeln!(
83 out,
84 "{names} <{mails}>",
85 names = join(self.name.iter(), ", "),
86 mails = join(self.email.iter(), ", ")
87 )?;
88 writeln!(out, "{} commits found", self.num_commits)?;
89 writeln!(
90 out,
91 "total time spent: {:.02}h ({:.02} 8h days, {:.02}%)",
92 self.hours,
93 self.hours / HOURS_PER_WORKDAY,
94 (self.hours / total_hours) * 100.0
95 )?;
96 if let Some(total) = total_files {
97 writeln!(
98 out,
99 "total files added/removed/modified: {}/{}/{} ({:.02}%)",
100 self.files.added,
101 self.files.removed,
102 self.files.modified,
103 (self.files.sum() / total.sum()) * 100.0
104 )?;
105 }
106 if let Some(total) = total_lines {
107 writeln!(
108 out,
109 "total lines added/removed: {}/{} ({:.02}%)",
110 self.lines.added,
111 self.lines.removed,
112 (self.lines.sum() / total.sum()) * 100.0
113 )?;
114 }
115 Ok(())
116 }
117}
118
119#[derive(Debug)]
120pub struct WorkByEmail {
121 pub name: &'static BStr,
122 pub email: &'static BStr,
123 pub hours: f32,
124 pub num_commits: u32,
125 pub files: FileStats,
126 pub lines: LineStats,
127}
128
129#[derive(Debug, Default, Copy, Clone)]
131pub struct FileStats {
132 pub added: usize,
134 pub removed: usize,
136 pub modified: usize,
138}
139
140#[derive(Debug, Default, Copy, Clone)]
142pub struct LineStats {
143 pub added: usize,
145 pub removed: usize,
147}
148
149impl FileStats {
150 pub fn add(&mut self, other: &FileStats) -> &mut Self {
151 self.added += other.added;
152 self.removed += other.removed;
153 self.modified += other.modified;
154 self
155 }
156
157 pub fn added(&self, other: &FileStats) -> Self {
158 let mut a = *self;
159 a.add(other);
160 a
161 }
162
163 pub fn sum(&self) -> f32 {
164 (self.added + self.removed + self.modified) as f32
165 }
166}
167
168impl LineStats {
169 pub fn add(&mut self, other: &LineStats) -> &mut Self {
170 self.added += other.added;
171 self.removed += other.removed;
172 self
173 }
174
175 pub fn added(&self, other: &LineStats) -> Self {
176 let mut a = *self;
177 a.add(other);
178 a
179 }
180
181 pub fn sum(&self) -> f32 {
182 (self.added + self.removed) as f32
183 }
184}
185
186pub type CommitIdx = u32;
188
189pub fn add_lines(line_stats: bool, lines_counter: &AtomicUsize, lines: &mut LineStats, id: gix::Id<'_>) {
190 if let Some(Ok(blob)) = line_stats.then(|| id.object()) {
191 let nl = blob.data.lines_with_terminator().count();
192 lines.added += nl;
193 lines_counter.fetch_add(nl, Ordering::Relaxed);
194 }
195}
196
197pub fn remove_lines(line_stats: bool, lines_counter: &AtomicUsize, lines: &mut LineStats, id: gix::Id<'_>) {
198 if let Some(Ok(blob)) = line_stats.then(|| id.object()) {
199 let nl = blob.data.lines_with_terminator().count();
200 lines.removed += nl;
201 lines_counter.fetch_add(nl, Ordering::Relaxed);
202 }
203}