diffy_fork_filenames/patch/
mod.rs1mod format;
2mod parse;
3
4pub use format::PatchFormatter;
5pub use parse::ParsePatchError;
6
7use std::{borrow::Cow, fmt, ops};
8
9const NO_NEWLINE_AT_EOF: &str = "\\ No newline at end of file";
10
11#[derive(PartialEq, Eq)]
13pub struct Patch<'a, T: ToOwned + ?Sized> {
14 original: Option<Filename<'a, T>>,
18 modified: Option<Filename<'a, T>>,
19 hunks: Vec<Hunk<'a, T>>,
20}
21
22impl<'a, T: ToOwned + ?Sized> Patch<'a, T> {
23 pub(crate) fn new<O, M>(
24 original: Option<O>,
25 modified: Option<M>,
26 hunks: Vec<Hunk<'a, T>>,
27 ) -> Self
28 where
29 O: Into<Cow<'a, T>>,
30 M: Into<Cow<'a, T>>,
31 {
32 let original = original.map(|o| Filename(o.into()));
33 let modified = modified.map(|m| Filename(m.into()));
34 Self {
35 original,
36 modified,
37 hunks,
38 }
39 }
40
41 pub fn original(&self) -> Option<&T> {
43 self.original.as_ref().map(AsRef::as_ref)
44 }
45
46 pub fn modified(&self) -> Option<&T> {
48 self.modified.as_ref().map(AsRef::as_ref)
49 }
50
51 pub fn hunks(&self) -> &[Hunk<'_, T>] {
53 &self.hunks
54 }
55}
56
57impl<T: AsRef<[u8]> + ToOwned + ?Sized> Patch<'_, T> {
58 pub fn to_bytes(&self) -> Vec<u8> {
63 let mut bytes = Vec::new();
64 PatchFormatter::new()
65 .write_patch_into(self, &mut bytes)
66 .unwrap();
67 bytes
68 }
69}
70
71impl<'a> Patch<'a, str> {
72 #[allow(clippy::should_implement_trait)]
92 pub fn from_str(s: &'a str) -> Result<Patch<'a, str>, ParsePatchError> {
93 parse::parse(s)
94 }
95}
96
97impl<'a> Patch<'a, [u8]> {
98 pub fn from_bytes(s: &'a [u8]) -> Result<Patch<'a, [u8]>, ParsePatchError> {
100 parse::parse_bytes(s)
101 }
102}
103
104impl<T: ToOwned + ?Sized> Clone for Patch<'_, T> {
105 fn clone(&self) -> Self {
106 Self {
107 original: self.original.clone(),
108 modified: self.modified.clone(),
109 hunks: self.hunks.clone(),
110 }
111 }
112}
113
114impl fmt::Display for Patch<'_, str> {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 write!(f, "{}", PatchFormatter::new().fmt_patch(self))
117 }
118}
119
120impl<T: ?Sized, O> fmt::Debug for Patch<'_, T>
121where
122 T: ToOwned<Owned = O> + fmt::Debug,
123 O: std::borrow::Borrow<T> + fmt::Debug,
124{
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 f.debug_struct("Patch")
127 .field("original", &self.original)
128 .field("modified", &self.modified)
129 .field("hunks", &self.hunks)
130 .finish()
131 }
132}
133
134#[derive(PartialEq, Eq)]
135struct Filename<'a, T: ToOwned + ?Sized>(Cow<'a, T>);
136
137const ESCAPED_CHARS: &[char] = &['\n', '\t', '\0', '\r', '\"', '\\'];
138const ESCAPED_CHARS_BYTES: &[u8] = &[b'\n', b'\t', b'\0', b'\r', b'\"', b'\\'];
139
140impl Filename<'_, str> {
141 fn needs_to_be_escaped(&self) -> bool {
142 self.0.contains(ESCAPED_CHARS)
143 }
144}
145
146impl<T: ToOwned + AsRef<[u8]> + ?Sized> Filename<'_, T> {
147 fn needs_to_be_escaped_bytes(&self) -> bool {
148 self.0
149 .as_ref()
150 .as_ref()
151 .iter()
152 .any(|b| ESCAPED_CHARS_BYTES.contains(b))
153 }
154
155 fn write_into<W: std::io::Write>(&self, mut w: W) -> std::io::Result<()> {
156 if self.needs_to_be_escaped_bytes() {
157 w.write_all(b"\"")?;
158 for b in self.0.as_ref().as_ref() {
159 if ESCAPED_CHARS_BYTES.contains(b) {
160 w.write_all(b"\\")?;
161 }
162 w.write_all(&[*b])?;
163 }
164 w.write_all(b"\"")?;
165 } else {
166 w.write_all(self.0.as_ref().as_ref())?;
167 }
168
169 Ok(())
170 }
171}
172
173impl<T: ToOwned + ?Sized> AsRef<T> for Filename<'_, T> {
174 fn as_ref(&self) -> &T {
175 &self.0
176 }
177}
178
179impl<T: ToOwned + ?Sized> ops::Deref for Filename<'_, T> {
180 type Target = T;
181
182 fn deref(&self) -> &Self::Target {
183 &self.0
184 }
185}
186
187impl<T: ToOwned + ?Sized> Clone for Filename<'_, T> {
188 fn clone(&self) -> Self {
189 Self(self.0.clone())
190 }
191}
192
193impl fmt::Display for Filename<'_, str> {
194 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195 use std::fmt::Write;
196 if self.needs_to_be_escaped() {
197 f.write_char('\"')?;
198 for c in self.0.chars() {
199 if ESCAPED_CHARS.contains(&c) {
200 f.write_char('\\')?;
201 }
202 f.write_char(c)?;
203 }
204 f.write_char('\"')?;
205 } else {
206 f.write_str(&self.0)?;
207 }
208
209 Ok(())
210 }
211}
212
213impl<T: ?Sized, O> fmt::Debug for Filename<'_, T>
214where
215 T: ToOwned<Owned = O> + fmt::Debug,
216 O: std::borrow::Borrow<T> + fmt::Debug,
217{
218 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219 f.debug_tuple("Filename").field(&self.0).finish()
220 }
221}
222
223#[derive(Debug, PartialEq, Eq)]
225pub struct Hunk<'a, T: ?Sized> {
226 old_range: HunkRange,
227 new_range: HunkRange,
228
229 function_context: Option<&'a T>,
230
231 lines: Vec<Line<'a, T>>,
232}
233
234fn hunk_lines_count<T: ?Sized>(lines: &[Line<'_, T>]) -> (usize, usize) {
235 lines.iter().fold((0, 0), |count, line| match line {
236 Line::Context(_) => (count.0 + 1, count.1 + 1),
237 Line::Delete(_) => (count.0 + 1, count.1),
238 Line::Insert(_) => (count.0, count.1 + 1),
239 })
240}
241
242impl<'a, T: ?Sized> Hunk<'a, T> {
243 pub(crate) fn new(
244 old_range: HunkRange,
245 new_range: HunkRange,
246 function_context: Option<&'a T>,
247 lines: Vec<Line<'a, T>>,
248 ) -> Self {
249 let (old_count, new_count) = hunk_lines_count(&lines);
250
251 assert_eq!(old_range.len, old_count);
252 assert_eq!(new_range.len, new_count);
253
254 Self {
255 old_range,
256 new_range,
257 function_context,
258 lines,
259 }
260 }
261
262 pub fn old_range(&self) -> HunkRange {
264 self.old_range
265 }
266
267 pub fn new_range(&self) -> HunkRange {
269 self.new_range
270 }
271
272 pub fn function_context(&self) -> Option<&T> {
274 self.function_context
275 }
276
277 pub fn lines(&self) -> &[Line<'a, T>] {
279 &self.lines
280 }
281}
282
283impl<T: ?Sized> Clone for Hunk<'_, T> {
284 fn clone(&self) -> Self {
285 Self {
286 old_range: self.old_range,
287 new_range: self.new_range,
288 function_context: self.function_context,
289 lines: self.lines.clone(),
290 }
291 }
292}
293
294#[derive(Copy, Clone, Debug, PartialEq, Eq)]
296pub struct HunkRange {
297 start: usize,
299 len: usize,
301}
302
303impl HunkRange {
304 pub(crate) fn new(start: usize, len: usize) -> Self {
305 Self { start, len }
306 }
307
308 pub fn range(&self) -> ops::Range<usize> {
310 self.start..self.end()
311 }
312
313 pub fn start(&self) -> usize {
315 self.start
316 }
317
318 pub fn end(&self) -> usize {
320 self.start + self.len
321 }
322
323 pub fn len(&self) -> usize {
325 self.len
326 }
327
328 pub fn is_empty(&self) -> bool {
330 self.len == 0
331 }
332}
333
334impl fmt::Display for HunkRange {
335 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336 write!(f, "{}", self.start)?;
337 if self.len != 1 {
338 write!(f, ",{}", self.len)?;
339 }
340 Ok(())
341 }
342}
343
344#[derive(Debug, PartialEq, Eq)]
349pub enum Line<'a, T: ?Sized> {
350 Context(&'a T),
352 Delete(&'a T),
354 Insert(&'a T),
356}
357
358impl<T: ?Sized> Copy for Line<'_, T> {}
359
360impl<T: ?Sized> Clone for Line<'_, T> {
361 fn clone(&self) -> Self {
362 *self
363 }
364}