diff_trees/
diff_entry.rs

1use std::path::Path;
2use std::path::PathBuf;
3
4use iddqd::IdOrdItem;
5use iddqd::id_upcast;
6use owo_colors::Style;
7
8use crate::DiffTag;
9use crate::DisplayDiffOpts;
10use crate::PathInfo;
11
12/// A single entry in a diff, identified by a path relative to the diff base directory.
13#[derive(Debug, Clone)]
14pub struct DiffEntry<'a> {
15    pub(crate) relative: PathBuf,
16    pub(crate) tag: DiffTag,
17    pub(crate) deleted: Option<PathInfo<'a>>,
18    pub(crate) inserted: Option<PathInfo<'a>>,
19}
20
21impl<'a> IdOrdItem for DiffEntry<'a> {
22    type Key<'b>
23        = &'b Path
24    where
25        Self: 'b;
26
27    fn key(&self) -> Self::Key<'_> {
28        self.relative.as_path()
29    }
30
31    id_upcast! {}
32}
33
34impl<'a> DiffEntry<'a> {
35    /// The path of this entry, relative to the old and new paths being diffed.
36    pub fn relative(&self) -> &Path {
37        &self.relative
38    }
39
40    /// The change made; was this path removed, inserted, or changed?
41    pub fn tag(&self) -> DiffTag {
42        self.tag
43    }
44
45    /// Information for the old path, if any.
46    pub fn deleted(&self) -> Option<&PathInfo<'a>> {
47        self.deleted.as_ref()
48    }
49
50    /// Information for the new path, if any.
51    pub fn inserted(&self) -> Option<&PathInfo<'a>> {
52        self.inserted.as_ref()
53    }
54
55    pub(crate) fn is_dir(&self) -> bool {
56        self.inserted
57            .as_ref()
58            .or(self.deleted.as_ref())
59            .map(|info| info.metadata.is_dir())
60            .unwrap_or(false)
61    }
62
63    pub(crate) fn format_path(&self) -> String {
64        let mut ret = self.relative.display().to_string();
65        if self.is_dir() {
66            ret.push('/');
67        }
68        ret
69    }
70
71    fn styled(&self, style: Style) -> owo_colors::Styled<String> {
72        style.style(format!("{} {}", self.tag.marker(), self.format_path()))
73    }
74
75    pub(crate) fn fmt_with(
76        &self,
77        f: &mut std::fmt::Formatter<'_>,
78        opts: &DisplayDiffOpts,
79    ) -> std::fmt::Result {
80        let style = if opts.color {
81            self.tag.style()
82        } else {
83            Style::new()
84        };
85
86        match self.tag {
87            DiffTag::Equal => {}
88            DiffTag::Delete => {
89                writeln!(f, "{}", self.styled(style))?;
90            }
91            DiffTag::Replace => {
92                // Directory entries are not very useful for me. This should probably also be
93                // customizable.
94                if !self.is_dir() {
95                    writeln!(f, "{}", self.styled(style))?;
96                }
97            }
98            DiffTag::Insert => {
99                writeln!(f, "{}", self.styled(style))?;
100            }
101        }
102        Ok(())
103    }
104
105    #[cfg(test)]
106    pub(crate) fn as_pair(&self) -> (&Path, DiffTag) {
107        (self.relative(), self.tag())
108    }
109}