git_commits/
change.rs

1use std::fmt;
2use std::path::{Path, PathBuf};
3
4macro_rules! change_impl {
5    (
6        $(($kind:ident, $as_kind:ident, $is_kind:ident)),*
7        $(,)?
8    ) => {
9        impl Change {
10            $(
11                #[inline]
12                pub fn $as_kind(&self) -> Option<&$kind> {
13                    match &self {
14                        Self::$kind(change) => Some(change),
15                        _ => None,
16                    }
17                }
18
19                #[inline]
20                pub const fn $is_kind(&self) -> bool {
21                    self.kind().$is_kind()
22                }
23            )*
24        }
25    };
26}
27
28macro_rules! change_kind_impl {
29    (
30        $kind:ident =>
31        $($field:ident),*
32        $(,)?
33    ) => {
34        impl $kind {
35            #[inline]
36            pub const fn kind(&self) -> ChangeKind {
37                ChangeKind::$kind
38            }
39
40            $(
41                change_kind_impl!(@expand $field);
42            )*
43        }
44    };
45
46    (@expand path) => {
47        /// Returns the path of the file.
48        #[inline]
49        pub fn path(&self) -> &Path {
50            &self.path.as_path()
51        }
52
53        /// Returns the path of the file.
54        #[inline]
55        pub fn into_path(self) -> PathBuf {
56            self.path
57        }
58    };
59
60    (@expand paths) => {
61        /// Returns the path of the file, before the change.
62        #[inline]
63        pub fn old_path(&self) -> &Path {
64            &self.old_path.as_path()
65        }
66
67        /// Returns the path of the file, after the change.
68        #[doc(alias = "path")]
69        #[inline]
70        pub fn new_path(&self) -> &Path {
71            &self.new_path.as_path()
72        }
73
74        /// Returns the `(old_path, new_path)`.
75        #[inline]
76        pub fn paths(&self) -> (&Path, &Path) {
77            (self.old_path.as_path(), self.new_path.as_path())
78        }
79
80        /// Returns the `(old_path, new_path)`.
81        #[inline]
82        pub fn into_paths(self) -> (PathBuf, PathBuf) {
83            (self.old_path, self.new_path)
84        }
85
86        #[doc(alias = "new_path")]
87        #[inline]
88        fn path(&self) -> &Path {
89            &self.new_path.as_path()
90        }
91
92        #[inline]
93        fn into_path(self) -> PathBuf {
94            self.new_path
95        }
96    };
97
98    (@expand size) => {
99        /// Returns the total size in bytes of the file, before the change.
100        #[inline]
101        pub const fn size(&self) -> usize {
102            self.size
103        }
104    };
105
106    (@expand sizes) => {
107        /// Returns the total size in bytes of the file, before the change.
108        #[inline]
109        pub const fn old_size(&self) -> usize {
110            self.old_size
111        }
112
113        /// Returns the total size in bytes of the file, after the change.
114        #[doc(alias = "size")]
115        #[inline]
116        pub const fn new_size(&self) -> usize {
117            self.new_size
118        }
119
120        /// Returns the `(old_size, new_size)`.
121        #[inline]
122        pub fn sizes(&self) -> (usize, usize) {
123            (self.old_size, self.new_size)
124        }
125
126        #[inline]
127        const fn size(&self) -> usize {
128            self.new_size
129        }
130    };
131}
132
133macro_rules! change {
134    (
135        $self:ident,
136        $change:ident => $expr:expr
137    ) => {
138        match $self {
139            Self::Added($change) => $expr,
140            Self::Modified($change) => $expr,
141            Self::Deleted($change) => $expr,
142            Self::Renamed($change) => $expr,
143        }
144    };
145}
146
147#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
148pub enum ChangeKind {
149    Added,
150    Modified,
151    Deleted,
152    Renamed,
153}
154
155impl ChangeKind {
156    /// ```text
157    /// Added => 'A'
158    /// Modified => 'M'
159    /// Deleted => 'D'
160    /// Renamed => 'R'
161    /// ```
162    #[inline]
163    pub const fn letter(self) -> char {
164        match self {
165            Self::Added => 'A',
166            Self::Modified => 'M',
167            Self::Deleted => 'D',
168            Self::Renamed => 'R',
169        }
170    }
171
172    /// ```text
173    /// `Added => '+'
174    /// `Modified => '~'
175    /// `Deleted => '-'
176    /// `Renamed => '>'
177    /// ```
178    #[inline]
179    pub const fn symbol(self) -> char {
180        match self {
181            Self::Added => '+',
182            Self::Modified => '~',
183            Self::Deleted => '-',
184            Self::Renamed => '>',
185        }
186    }
187
188    #[inline]
189    pub const fn is_added(self) -> bool {
190        matches!(self, Self::Added)
191    }
192
193    #[inline]
194    pub const fn is_modified(self) -> bool {
195        matches!(self, Self::Modified)
196    }
197
198    #[inline]
199    pub const fn is_deleted(self) -> bool {
200        matches!(self, Self::Deleted)
201    }
202
203    #[inline]
204    pub const fn is_renamed(self) -> bool {
205        matches!(self, Self::Renamed)
206    }
207}
208
209impl fmt::Display for ChangeKind {
210    #[inline]
211    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212        self.letter().fmt(f)
213    }
214}
215
216#[derive(PartialEq, Eq, Clone, Debug)]
217pub enum Change {
218    Added(Added),
219    Modified(Modified),
220    Deleted(Deleted),
221    Renamed(Renamed),
222}
223
224impl Change {
225    #[inline]
226    pub const fn kind(&self) -> ChangeKind {
227        match self {
228            Self::Added(_) => ChangeKind::Added,
229            Self::Modified(_) => ChangeKind::Modified,
230            Self::Deleted(_) => ChangeKind::Deleted,
231            Self::Renamed(_) => ChangeKind::Renamed,
232        }
233    }
234
235    /// Returns the path of the file, after the change.
236    #[doc(alias = "new_path")]
237    #[inline]
238    pub fn path(&self) -> &Path {
239        change!(self, change => change.path())
240    }
241
242    /// Returns the path of the file, after the change.
243    #[inline]
244    pub fn into_path(self) -> PathBuf {
245        change!(self, change => change.into_path())
246    }
247
248    /// Returns the path of the file, before the change.
249    ///
250    /// Only <code>[Change]::[Renamed]</code>
251    /// has an [`old_path`](Renamed::old_path).
252    #[inline]
253    pub fn old_path(&self) -> Option<&Path> {
254        match self {
255            Self::Renamed(change) => Some(change.old_path()),
256            _ => None,
257        }
258    }
259
260    /// Returns the `(old_path, path)`.
261    ///
262    /// Only <code>[Change]::[Renamed]</code>
263    /// has an [`old_path`](Renamed::old_path).
264    #[inline]
265    pub fn paths(&self) -> (Option<&Path>, &Path) {
266        match self {
267            Self::Added(change) => (None, change.path()),
268            Self::Modified(change) => (None, change.path()),
269            Self::Deleted(change) => (None, change.path()),
270            Self::Renamed(change) => {
271                let (old_path, new_path) = change.paths();
272                (Some(old_path), new_path)
273            }
274        }
275    }
276
277    /// Returns the `(old_path, path)`.
278    ///
279    /// Only <code>[Change]::[Renamed]</code>
280    /// has an [`old_path`](Renamed::old_path).
281    #[inline]
282    pub fn into_paths(self) -> (Option<PathBuf>, PathBuf) {
283        match self {
284            Self::Added(change) => (None, change.into_path()),
285            Self::Modified(change) => (None, change.into_path()),
286            Self::Deleted(change) => (None, change.into_path()),
287            Self::Renamed(change) => {
288                let (old_path, new_path) = change.into_paths();
289                (Some(old_path), new_path)
290            }
291        }
292    }
293
294    /// Returns the total size in bytes of the file, after the change.
295    #[doc(alias = "new_size")]
296    #[inline]
297    pub const fn size(&self) -> usize {
298        change!(self, change => change.size())
299    }
300
301    /// Returns the total size in bytes of the file, before the change.
302    ///
303    /// Only <code>[Change]::[Modified]</code>
304    /// has an [`old_size`](Modified::old_size).
305    #[inline]
306    pub const fn old_size(&self) -> Option<usize> {
307        match self {
308            Self::Modified(change) => Some(change.old_size()),
309            _ => None,
310        }
311    }
312
313    /// Returns the `(old_size, size)`.
314    ///
315    /// Only <code>[Change]::[Modified]</code>
316    /// has an [`old_size`](Modified::old_size).
317    #[inline]
318    pub const fn sizes(&self) -> (Option<usize>, usize) {
319        match self {
320            Self::Added(change) => (None, change.size()),
321            Self::Modified(change) => (Some(change.old_size()), change.size()),
322            Self::Deleted(change) => (None, change.size()),
323            Self::Renamed(change) => (None, change.size()),
324        }
325    }
326}
327
328change_impl!(
329    (Added, as_added, is_added),
330    (Modified, as_modified, is_modified),
331    (Deleted, as_deleted, is_deleted),
332    (Renamed, as_renamed, is_renamed),
333);
334
335#[derive(PartialEq, Eq, Clone, Debug)]
336pub struct Added {
337    /// The path of the added file.
338    pub(crate) path: PathBuf,
339    /// Total size in bytes.
340    pub(crate) size: usize,
341}
342
343#[derive(PartialEq, Eq, Clone, Debug)]
344pub struct Modified {
345    /// The path of the modified file.
346    pub(crate) path: PathBuf,
347    /// Total size in bytes.
348    pub(crate) old_size: usize,
349    /// Total size in bytes.
350    pub(crate) new_size: usize,
351}
352
353#[derive(PartialEq, Eq, Clone, Debug)]
354pub struct Deleted {
355    /// The path of the deleted file.
356    pub(crate) path: PathBuf,
357    /// Total size in bytes.
358    pub(crate) size: usize,
359}
360
361#[derive(PartialEq, Eq, Clone, Debug)]
362pub struct Renamed {
363    /// The path before the renaming.
364    pub(crate) old_path: PathBuf,
365    /// The path after the renaming.
366    pub(crate) new_path: PathBuf,
367    /// Total size in bytes.
368    pub(crate) size: usize,
369}
370
371change_kind_impl!(Added => path, size);
372change_kind_impl!(Modified => path, sizes);
373change_kind_impl!(Deleted => path, size);
374change_kind_impl!(Renamed => paths, size);
375
376impl fmt::Display for Change {
377    #[inline]
378    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
379        match self {
380            Self::Added(change) => change.fmt(f),
381            Self::Modified(change) => change.fmt(f),
382            Self::Deleted(change) => change.fmt(f),
383            Self::Renamed(change) => change.fmt(f),
384        }
385    }
386}
387
388impl fmt::Display for Added {
389    #[inline]
390    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391        write!(
392            f,
393            "{} {} ({} bytes)",
394            ChangeKind::Added,
395            self.path.display(),
396            self.size,
397        )
398    }
399}
400
401impl fmt::Display for Modified {
402    #[inline]
403    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404        write!(
405            f,
406            "{} {} ({} -> {} bytes)",
407            ChangeKind::Modified,
408            self.path.display(),
409            self.old_size,
410            self.new_size,
411        )
412    }
413}
414
415impl fmt::Display for Deleted {
416    #[inline]
417    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
418        write!(
419            f,
420            "{} {} ({} bytes)",
421            ChangeKind::Deleted,
422            self.path.display(),
423            self.size,
424        )
425    }
426}
427
428impl fmt::Display for Renamed {
429    #[inline]
430    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
431        write!(
432            f,
433            "{} {} -> {} ({} bytes)",
434            ChangeKind::Added,
435            self.old_path.display(),
436            self.new_path.display(),
437            self.size,
438        )
439    }
440}
441
442impl From<Added> for Change {
443    #[inline]
444    fn from(change: Added) -> Self {
445        Self::Added(change)
446    }
447}
448
449impl From<Modified> for Change {
450    #[inline]
451    fn from(change: Modified) -> Self {
452        Self::Modified(change)
453    }
454}
455
456impl From<Deleted> for Change {
457    #[inline]
458    fn from(change: Deleted) -> Self {
459        Self::Deleted(change)
460    }
461}
462
463impl From<Renamed> for Change {
464    #[inline]
465    fn from(change: Renamed) -> Self {
466        Self::Renamed(change)
467    }
468}