java_diff_utils_rs/unifieddiff/
unified_diff_writer.rs1use std::io::{self, Write};
4
5use super::unified_diff::UnifiedDiff;
6use crate::patch::delta::Delta;
7
8pub struct UnifiedDiffWriter;
10
11impl UnifiedDiffWriter {
12 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 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}