Skip to main content

agm_core/
git_stat.rs

1use std::path::PathBuf;
2
3use crate::ParseError;
4
5#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
6pub struct GitStat {
7    pub insertions: usize,
8    pub deletions: usize,
9    pub new_files: usize,
10    pub is_worktree: bool,
11}
12
13impl GitStat {
14    pub fn parse_line(line: &str) -> Result<(PathBuf, Self), ParseError> {
15        let mut parts = line.rsplitn(5, ' ');
16        let is_worktree = parts
17            .next()
18            .ok_or(ParseError::Missing("worktree field"))
19            .and_then(|v| {
20                v.parse::<u8>().map_err(|_| ParseError::Invalid {
21                    field: "worktree",
22                    value: format!("{v:?}"),
23                })
24            })?
25            != 0;
26        let new_files = parts
27            .next()
28            .ok_or(ParseError::Missing("new_files field"))
29            .and_then(|v| {
30                v.parse().map_err(|_| ParseError::Invalid {
31                    field: "new_files",
32                    value: format!("{v:?}"),
33                })
34            })?;
35        let deletions = parts
36            .next()
37            .ok_or(ParseError::Missing("deletions field"))
38            .and_then(|v| {
39                v.parse().map_err(|_| ParseError::Invalid {
40                    field: "deletions",
41                    value: format!("{v:?}"),
42                })
43            })?;
44        let insertions = parts
45            .next()
46            .ok_or(ParseError::Missing("insertions field"))
47            .and_then(|v| {
48                v.parse().map_err(|_| ParseError::Invalid {
49                    field: "insertions",
50                    value: format!("{v:?}"),
51                })
52            })?;
53        let path = PathBuf::from(parts.next().ok_or(ParseError::Missing("path"))?);
54        Ok((
55            path,
56            Self {
57                insertions,
58                deletions,
59                new_files,
60                is_worktree,
61            },
62        ))
63    }
64}
65
66impl std::fmt::Display for GitStat {
67    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        write!(
69            f,
70            "{} {} {} {}",
71            self.insertions,
72            self.deletions,
73            self.new_files,
74            u8::from(self.is_worktree),
75        )
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use rstest::rstest;
82
83    use super::*;
84
85    #[rstest]
86    #[case("/home/user/project 10 5 2 1", "/home/user/project", 10, 5, 2, true)]
87    #[case("/home/user/my project 10 5 2 0", "/home/user/my project", 10, 5, 2, false)]
88    fn test_git_stat_parse_line_works_as_expected(
89        #[case] line: &str,
90        #[case] expected_path: &str,
91        #[case] insertions: usize,
92        #[case] deletions: usize,
93        #[case] new_files: usize,
94        #[case] is_worktree: bool,
95    ) {
96        let expected_stat = GitStat {
97            insertions,
98            deletions,
99            new_files,
100            is_worktree,
101        };
102        assert2::assert!(let Ok((path, stat)) = GitStat::parse_line(line));
103        pretty_assertions::assert_eq!((path, stat), (PathBuf::from(expected_path), expected_stat));
104    }
105}