gitoxide_core/hours/
util.rs

1use 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
45/// Combine all iterator elements into one String, separated by `sep`.
46///
47/// Use the `Display` implementation of each element.
48///
49/// extracted from
50/// https://github.com/rust-itertools/itertools/blob/762643f1be2217140a972745cf4d6ed69435f722/src/lib.rs#L2295-L2324
51fn 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            // estimate lower bound of capacity needed
62            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/// File statistics for a particular commit.
130#[derive(Debug, Default, Copy, Clone)]
131pub struct FileStats {
132    /// amount of added files
133    pub added: usize,
134    /// amount of removed files
135    pub removed: usize,
136    /// amount of modified files
137    pub modified: usize,
138}
139
140/// Line statistics for a particular commit.
141#[derive(Debug, Default, Copy, Clone)]
142pub struct LineStats {
143    /// amount of added lines
144    pub added: usize,
145    /// amount of removed lines
146    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
186/// An index able to address any commit
187pub 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}