Skip to main content

sheets_diff/core/
unified_format.rs

1use std::fmt;
2
3#[cfg(feature = "serde_derive")]
4use serde::{Deserialize, Serialize};
5
6use super::diff::Diff;
7
8/// Unified diff ready for further formatting or splitting.
9#[derive(Clone, Debug)]
10#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
11pub struct UnifiedDiff {
12    pub content: Vec<UnifiedDiffContent>,
13}
14
15/// Unified diff with prefix markers applied to each line.
16#[derive(Clone, Debug)]
17#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
18pub struct FormattedUnifiedDiff {
19    pub content: Vec<UnifiedDiffContent>,
20}
21
22/// One sheet's worth of unified diff content.
23#[derive(Clone, Debug)]
24#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
25pub struct UnifiedDiffContent {
26    pub old_title: String,
27    pub new_title: String,
28    pub lines: Vec<UnifiedDiffLine>,
29}
30
31/// A single line in a unified diff.
32#[derive(Clone, Debug)]
33#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
34pub struct UnifiedDiffLine {
35    pub pos: Option<String>,
36    pub old: Option<String>,
37    pub new: Option<String>,
38}
39
40/// Unified diff split into separate old and new views.
41#[derive(Clone, Debug)]
42#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
43pub struct SplitUnifiedDiff {
44    pub old: Vec<SplitUnifiedDiffContent>,
45    pub new: Vec<SplitUnifiedDiffContent>,
46}
47
48/// One sheet's content in a split unified diff.
49#[derive(Clone, Debug)]
50#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
51pub struct SplitUnifiedDiffContent {
52    pub title: String,
53    pub lines: Vec<SplitUnifiedDiffLine>,
54}
55
56/// A single line in a split unified diff.
57#[derive(Clone, Debug)]
58#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
59pub struct SplitUnifiedDiffLine {
60    pub pos: Option<String>,
61    pub text: Option<String>,
62}
63
64impl UnifiedDiff {
65    /// Applies `---`/`+++`/`-`/`+`/`@@` prefix markers to all fields.
66    pub fn format(&self) -> FormattedUnifiedDiff {
67        let content = self
68            .content
69            .iter()
70            .map(|x| {
71                let old_title = format!("--- {}", &x.old_title);
72                let new_title = format!("+++ {}", &x.new_title);
73
74                let lines = x
75                    .lines
76                    .iter()
77                    .map(|x| UnifiedDiffLine {
78                        pos: x.pos.as_ref().map(|pos| format!("@@ {} @@", pos)),
79                        old: x.old.as_ref().map(|old| format!("- {}", old)),
80                        new: x.new.as_ref().map(|new| format!("+ {}", new)),
81                    })
82                    .collect();
83
84                UnifiedDiffContent { old_title, new_title, lines }
85            })
86            .collect();
87        FormattedUnifiedDiff { content }
88    }
89
90    /// Splits into separate old and new content views.
91    pub fn split(&self) -> SplitUnifiedDiff {
92        let old = self
93            .content
94            .iter()
95            .map(|x| SplitUnifiedDiffContent {
96                title: x.old_title.clone(),
97                lines: x
98                    .lines
99                    .iter()
100                    .map(|x| SplitUnifiedDiffLine {
101                        pos: x.pos.as_ref().map(|p| p.to_owned()),
102                        text: x.old.as_ref().map(|t| t.to_owned()),
103                    })
104                    .collect(),
105            })
106            .collect();
107
108        let new = self
109            .content
110            .iter()
111            .map(|x| SplitUnifiedDiffContent {
112                title: x.new_title.clone(),
113                lines: x
114                    .lines
115                    .iter()
116                    .map(|x| SplitUnifiedDiffLine {
117                        pos: x.pos.as_ref().map(|p| p.to_owned()),
118                        text: x.new.as_ref().map(|t| t.to_owned()),
119                    })
120                    .collect(),
121            })
122            .collect();
123
124        SplitUnifiedDiff { old, new }
125    }
126}
127
128impl fmt::Display for FormattedUnifiedDiff {
129    /// Renders the unified diff as a plain-text string.
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        for x in &self.content {
132            writeln!(f, "{}", &x.old_title)?;
133            writeln!(f, "{}", &x.new_title)?;
134            for x in &x.lines {
135                if let Some(pos) = &x.pos {
136                    writeln!(f, "{}", pos)?;
137                }
138                if let Some(old) = &x.old {
139                    writeln!(f, "{}", old)?;
140                }
141                if let Some(new) = &x.new {
142                    writeln!(f, "{}", new)?;
143                }
144            }
145        }
146        Ok(())
147    }
148}
149
150/// Builds a [`UnifiedDiff`] from the raw [`Diff`] result.
151pub fn unified_diff(diff: &Diff) -> UnifiedDiff {
152    let mut ret: Vec<UnifiedDiffContent> = vec![];
153
154    if !diff.sheet_diff.is_empty() {
155        let lines = diff
156            .sheet_diff
157            .iter()
158            .map(|x| UnifiedDiffLine {
159                pos: None,
160                old: x.old.clone(),
161                new: x.new.clone(),
162            })
163            .collect();
164
165        ret.push(UnifiedDiffContent {
166            old_title: format!("{} (sheet names)", diff.old_filepath),
167            new_title: format!("{} (sheet names)", diff.new_filepath),
168            lines,
169        });
170    }
171
172    let cell_diffs_content: Vec<UnifiedDiffContent> = diff
173        .cell_diffs
174        .iter()
175        .map(|x| {
176            let lines = x
177                .cells
178                .iter()
179                .map(|x| UnifiedDiffLine {
180                    pos: Some(format!("{}({},{}) {}", x.addr, x.row, x.col, x.kind)),
181                    old: x.old.clone(),
182                    new: x.new.clone(),
183                })
184                .collect();
185
186            UnifiedDiffContent {
187                old_title: format!("{} [{}]", diff.old_filepath, x.sheet),
188                new_title: format!("{} [{}]", diff.new_filepath, x.sheet),
189                lines,
190            }
191        })
192        .collect();
193
194    ret.extend(cell_diffs_content);
195
196    UnifiedDiff { content: ret }
197}