1use alloc::borrow::ToOwned;
2use core::fmt::Display;
3use core::fmt::Formatter;
4use core::fmt::Result;
5
6#[cfg(feature = "std")]
7use std::io;
8
9use super::Hunk;
10use super::Line;
11use super::NO_NEWLINE_AT_EOF;
12use super::Patch;
13#[cfg(feature = "color")]
14use super::style;
15
16#[derive(Debug)]
43pub struct PatchFormatter {
44 #[cfg(feature = "color")]
45 with_color: bool,
46 with_missing_newline_message: bool,
47 suppress_blank_empty: bool,
48}
49
50impl PatchFormatter {
51 pub fn new() -> Self {
53 Self {
54 #[cfg(feature = "color")]
55 with_color: false,
56 with_missing_newline_message: true,
57
58 suppress_blank_empty: true,
61 }
62 }
63
64 #[cfg(feature = "color")]
66 #[cfg_attr(docsrs, doc(cfg(feature = "color")))]
67 pub fn with_color(mut self) -> Self {
68 self.with_color = true;
69 self
70 }
71
72 pub fn missing_newline_message(mut self, enable: bool) -> Self {
81 self.with_missing_newline_message = enable;
82 self
83 }
84
85 pub fn suppress_blank_empty(mut self, enable: bool) -> Self {
95 self.suppress_blank_empty = enable;
96 self
97 }
98
99 pub fn fmt_patch<'a>(&'a self, patch: &'a Patch<'a, str>) -> impl Display + 'a {
101 PatchDisplay { f: self, patch }
102 }
103
104 #[cfg(feature = "std")]
110 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
111 pub fn write_patch_into<T: ToOwned + AsRef<[u8]> + ?Sized, W: io::Write>(
112 &self,
113 patch: &Patch<'_, T>,
114 w: W,
115 ) -> io::Result<()> {
116 PatchDisplay { f: self, patch }.write_into(w)
117 }
118
119 fn fmt_hunk<'a>(&'a self, hunk: &'a Hunk<'a, str>) -> impl Display + 'a {
120 HunkDisplay { f: self, hunk }
121 }
122
123 #[cfg(feature = "std")]
124 fn write_hunk_into<T: AsRef<[u8]> + ?Sized, W: io::Write>(
125 &self,
126 hunk: &Hunk<'_, T>,
127 w: W,
128 ) -> io::Result<()> {
129 HunkDisplay { f: self, hunk }.write_into(w)
130 }
131
132 fn fmt_line<'a>(&'a self, line: &'a Line<'a, str>) -> impl Display + 'a {
133 LineDisplay { f: self, line }
134 }
135
136 #[cfg(feature = "std")]
137 fn write_line_into<T: AsRef<[u8]> + ?Sized, W: io::Write>(
138 &self,
139 line: &Line<'_, T>,
140 w: W,
141 ) -> io::Result<()> {
142 LineDisplay { f: self, line }.write_into(w)
143 }
144}
145
146impl Default for PatchFormatter {
147 fn default() -> Self {
148 Self::new()
149 }
150}
151
152struct PatchDisplay<'a, T: ToOwned + ?Sized> {
153 f: &'a PatchFormatter,
154 patch: &'a Patch<'a, T>,
155}
156
157#[cfg(feature = "std")]
158impl<T: ToOwned + AsRef<[u8]> + ?Sized> PatchDisplay<'_, T> {
159 fn write_into<W: io::Write>(&self, mut w: W) -> io::Result<()> {
160 if self.patch.original.is_some() || self.patch.modified.is_some() {
161 #[cfg(feature = "color")]
162 if self.f.with_color {
163 write!(w, "{}", style::PATCH_HEADER)?;
164 }
165 if let Some(original) = &self.patch.original {
166 write!(w, "--- ")?;
167 original.write_into(&mut w)?;
168 writeln!(w)?;
169 }
170 if let Some(modified) = &self.patch.modified {
171 write!(w, "+++ ")?;
172 modified.write_into(&mut w)?;
173 writeln!(w)?;
174 }
175 #[cfg(feature = "color")]
176 if self.f.with_color {
177 write!(w, "{:#}", style::PATCH_HEADER)?;
178 }
179 }
180
181 for hunk in &self.patch.hunks {
182 self.f.write_hunk_into(hunk, &mut w)?;
183 }
184
185 Ok(())
186 }
187}
188
189impl Display for PatchDisplay<'_, str> {
190 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
191 if self.patch.original.is_some() || self.patch.modified.is_some() {
192 #[cfg(feature = "color")]
193 if self.f.with_color {
194 write!(f, "{}", style::PATCH_HEADER)?;
195 }
196 if let Some(original) = &self.patch.original {
197 writeln!(f, "--- {original}")?;
198 }
199 if let Some(modified) = &self.patch.modified {
200 writeln!(f, "+++ {modified}")?;
201 }
202 #[cfg(feature = "color")]
203 if self.f.with_color {
204 write!(f, "{:#}", style::PATCH_HEADER)?;
205 }
206 }
207
208 for hunk in &self.patch.hunks {
209 write!(f, "{}", self.f.fmt_hunk(hunk))?;
210 }
211
212 Ok(())
213 }
214}
215
216struct HunkDisplay<'a, T: ?Sized> {
217 f: &'a PatchFormatter,
218 hunk: &'a Hunk<'a, T>,
219}
220
221#[cfg(feature = "std")]
222impl<T: AsRef<[u8]> + ?Sized> HunkDisplay<'_, T> {
223 fn write_into<W: io::Write>(&self, mut w: W) -> io::Result<()> {
224 #[cfg(feature = "color")]
225 if self.f.with_color {
226 write!(w, "{}", style::HUNK_HEADER)?;
227 }
228 write!(w, "@@ -{} +{} @@", self.hunk.old_range, self.hunk.new_range)?;
229 #[cfg(feature = "color")]
230 if self.f.with_color {
231 write!(w, "{:#}", style::HUNK_HEADER)?;
232 }
233
234 if let Some(ctx) = self.hunk.function_context {
235 write!(w, " ")?;
236 #[cfg(feature = "color")]
237 if self.f.with_color {
238 write!(w, "{}", style::FUNCTION_CONTEXT)?;
239 }
240 write!(w, " ")?;
241 w.write_all(ctx.as_ref())?;
242 #[cfg(feature = "color")]
243 if self.f.with_color {
244 write!(w, "{:#}", style::FUNCTION_CONTEXT)?;
245 }
246 }
247 writeln!(w)?;
248
249 for line in &self.hunk.lines {
250 self.f.write_line_into(line, &mut w)?;
251 }
252
253 Ok(())
254 }
255}
256
257impl Display for HunkDisplay<'_, str> {
258 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
259 #[cfg(feature = "color")]
260 if self.f.with_color {
261 write!(f, "{}", style::HUNK_HEADER)?;
262 }
263 write!(f, "@@ -{} +{} @@", self.hunk.old_range, self.hunk.new_range)?;
264 #[cfg(feature = "color")]
265 if self.f.with_color {
266 write!(f, "{:#}", style::HUNK_HEADER)?;
267 }
268
269 if let Some(ctx) = self.hunk.function_context {
270 write!(f, " ")?;
271 #[cfg(feature = "color")]
272 if self.f.with_color {
273 write!(f, "{}", style::FUNCTION_CONTEXT)?;
274 }
275 write!(f, " {ctx}")?;
276 #[cfg(feature = "color")]
277 if self.f.with_color {
278 write!(f, "{:#}", style::FUNCTION_CONTEXT)?;
279 }
280 }
281 writeln!(f)?;
282
283 for line in &self.hunk.lines {
284 write!(f, "{}", self.f.fmt_line(line))?;
285 }
286
287 Ok(())
288 }
289}
290
291struct LineDisplay<'a, T: ?Sized> {
292 f: &'a PatchFormatter,
293 line: &'a Line<'a, T>,
294}
295
296#[cfg(feature = "std")]
297impl<T: AsRef<[u8]> + ?Sized> LineDisplay<'_, T> {
298 fn write_into<W: io::Write>(&self, mut w: W) -> io::Result<()> {
299 #[cfg(feature = "color")]
300 let (sign, line, style) = match self.line {
301 Line::Context(line) => (' ', line.as_ref(), style::CONTEXT),
302 Line::Delete(line) => ('-', line.as_ref(), style::DELETE),
303 Line::Insert(line) => ('+', line.as_ref(), style::INSERT),
304 };
305 #[cfg(not(feature = "color"))]
306 let (sign, line) = match self.line {
307 Line::Context(line) => (' ', line.as_ref()),
308 Line::Delete(line) => ('-', line.as_ref()),
309 Line::Insert(line) => ('+', line.as_ref()),
310 };
311
312 #[cfg(feature = "color")]
313 if self.f.with_color {
314 write!(w, "{style}")?;
315 }
316
317 if self.f.suppress_blank_empty && sign == ' ' && line == b"\n" {
318 w.write_all(line)?;
319 } else {
320 write!(w, "{sign}")?;
321 w.write_all(line)?;
322 }
323
324 #[cfg(feature = "color")]
325 if self.f.with_color {
326 write!(w, "{style:#}")?;
327 }
328
329 if !line.ends_with(b"\n") {
330 writeln!(w)?;
331 if self.f.with_missing_newline_message {
332 writeln!(w, "{NO_NEWLINE_AT_EOF}")?;
333 }
334 }
335
336 Ok(())
337 }
338}
339
340impl Display for LineDisplay<'_, str> {
341 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
342 #[cfg(feature = "color")]
343 let (sign, line, style) = match self.line {
344 Line::Context(line) => (' ', line, style::CONTEXT),
345 Line::Delete(line) => ('-', line, style::DELETE),
346 Line::Insert(line) => ('+', line, style::INSERT),
347 };
348 #[cfg(not(feature = "color"))]
349 let (sign, line) = match self.line {
350 Line::Context(line) => (' ', line),
351 Line::Delete(line) => ('-', line),
352 Line::Insert(line) => ('+', line),
353 };
354
355 #[cfg(feature = "color")]
356 if self.f.with_color {
357 write!(f, "{style}")?;
358 }
359
360 if self.f.suppress_blank_empty && sign == ' ' && *line == "\n" {
361 write!(f, "{line}")?;
362 } else {
363 write!(f, "{sign}{line}")?;
364 }
365
366 #[cfg(feature = "color")]
367 if self.f.with_color {
368 write!(f, "{style:#}")?;
369 }
370
371 if !line.ends_with('\n') {
372 writeln!(f)?;
373 if self.f.with_missing_newline_message {
374 writeln!(f, "{NO_NEWLINE_AT_EOF}")?;
375 }
376 }
377
378 Ok(())
379 }
380}