Skip to main content

java_diff_utils_rs/unifieddiff/
unified_diff_writer.rs

1//! Writer utility for exporting a `UnifiedDiff` back into standard unified diff format strings or streams.
2
3use std::io::{self, Write};
4
5use super::unified_diff::UnifiedDiff;
6use crate::patch::delta::Delta;
7
8/// Writer for outputting unified diff formatting.
9pub struct UnifiedDiffWriter;
10
11impl UnifiedDiffWriter {
12    /// Writes a `UnifiedDiff` out to a std::io::Write output stream.
13    pub fn write<W, F>(
14        diff: &UnifiedDiff,
15        original_lines_provider: F,
16        writer: &mut W,
17        context_size: usize,
18    ) -> io::Result<()>
19    where
20        W: Write,
21        F: Fn(Option<&str>) -> Vec<String>,
22    {
23        Self::write_consumer(
24            diff,
25            original_lines_provider,
26            |line| {
27                let _ = writeln!(writer, "{}", line);
28            },
29            context_size,
30        )
31    }
32
33    /// Writes a `UnifiedDiff` using a line consumer closure.
34    pub fn write_consumer<F, C>(
35        diff: &UnifiedDiff,
36        original_lines_provider: F,
37        mut writer: C,
38        context_size: usize,
39    ) -> io::Result<()>
40    where
41        F: Fn(Option<&str>) -> Vec<String>,
42        C: FnMut(&str),
43    {
44        if let Some(header) = diff.header() {
45            writer(header);
46        }
47
48        for file in diff.files() {
49            let patch_deltas = file.patch().deltas();
50            if !patch_deltas.is_empty() {
51                if let Some(cmd) = file.diff_command() {
52                    writer(cmd);
53                }
54                if let Some(index) = file.index() {
55                    writer(&format!("index {}", index));
56                }
57
58                let from_file_str = match file.from_file() {
59                    None => "/dev/null",
60                    Some(f) if f.is_empty() => "/dev/null",
61                    Some(f) => f,
62                };
63                writer(&format!("--- {}", from_file_str));
64
65                if let Some(to_file) = file.to_file() {
66                    writer(&format!("+++ {}", to_file));
67                }
68
69                let original_lines = original_lines_provider(file.from_file());
70                let mut deltas: Vec<&Delta<String>> = Vec::new();
71
72                let mut delta = &patch_deltas[0];
73                deltas.push(delta);
74
75                if patch_deltas.len() > 1 {
76                    for next_delta in patch_deltas.iter().skip(1) {
77                        let position = delta.source().position();
78
79                        if (position + delta.source().size() + context_size)
80                            >= next_delta.source().position().saturating_sub(context_size)
81                        {
82                            deltas.push(next_delta);
83                        } else {
84                            Self::process_deltas(
85                                &mut writer,
86                                &original_lines,
87                                &deltas,
88                                context_size,
89                            );
90                            deltas.clear();
91                            deltas.push(next_delta);
92                        }
93                        delta = next_delta;
94                    }
95                }
96
97                Self::process_deltas(&mut writer, &original_lines, &deltas, context_size);
98            }
99        }
100
101        if let Some(tail) = diff.tail() {
102            writer("--");
103            writer(tail);
104        }
105
106        Ok(())
107    }
108
109    fn process_deltas<C>(
110        writer: &mut C,
111        orig_lines: &[String],
112        deltas: &[&Delta<String>],
113        context_size: usize,
114    ) where
115        C: FnMut(&str),
116    {
117        if deltas.is_empty() {
118            return;
119        }
120
121        let mut buffer: Vec<String> = Vec::new();
122        let mut orig_total = 0usize;
123        let mut rev_total = 0usize;
124
125        let cur_delta = deltas[0];
126
127        let context_start = cur_delta.source().position().saturating_sub(context_size);
128
129        for line in context_start..cur_delta.source().position().min(orig_lines.len()) {
130            buffer.push(format!(" {}", orig_lines[line]));
131            orig_total += 1;
132            rev_total += 1;
133        }
134
135        Self::get_delta_text(&mut |txt| buffer.push(txt.to_string()), cur_delta);
136        orig_total += cur_delta.source().lines().len();
137        rev_total += cur_delta.target().lines().len();
138
139        let mut last_delta = cur_delta;
140        for &next_delta in deltas.iter().skip(1) {
141            let intermediate_start =
142                last_delta.source().position() + last_delta.source().lines().len();
143
144            for line in intermediate_start..next_delta.source().position().min(orig_lines.len()) {
145                buffer.push(format!(" {}", orig_lines[line]));
146                orig_total += 1;
147                rev_total += 1;
148            }
149
150            Self::get_delta_text(&mut |txt| buffer.push(txt.to_string()), next_delta);
151            orig_total += next_delta.source().lines().len();
152            rev_total += next_delta.target().lines().len();
153            last_delta = next_delta;
154        }
155
156        let post_context_start = last_delta.source().position() + last_delta.source().lines().len();
157        let post_context_end = (post_context_start + context_size).min(orig_lines.len());
158
159        for line in post_context_start..post_context_end {
160            buffer.push(format!(" {}", orig_lines[line]));
161            orig_total += 1;
162            rev_total += 1;
163        }
164
165        let orig_start = if orig_total == 0 {
166            0
167        } else {
168            let pos_plus_one = cur_delta.source().position() + 1;
169            if pos_plus_one > context_size {
170                pos_plus_one - context_size
171            } else {
172                1
173            }
174        };
175
176        let rev_start = if rev_total == 0 {
177            0
178        } else {
179            let rev_pos_plus_one = cur_delta.target().position() + 1;
180            if rev_pos_plus_one > context_size {
181                rev_pos_plus_one - context_size
182            } else {
183                1
184            }
185        };
186
187        writer(&format!(
188            "@@ -{},{} +{},{} @@",
189            orig_start, orig_total, rev_start, rev_total
190        ));
191
192        for txt in buffer {
193            writer(&txt);
194        }
195    }
196
197    fn get_delta_text<C>(writer: &mut C, delta: &Delta<String>)
198    where
199        C: FnMut(&str),
200    {
201        for line in delta.source().lines() {
202            writer(&format!("-{}", line));
203        }
204        for line in delta.target().lines() {
205            writer(&format!("+{}", line));
206        }
207    }
208}