|
1 | 1 | use std::collections::VecDeque;
|
| 2 | +use std::fmt; |
2 | 3 | use std::io;
|
3 | 4 | use std::io::Write;
|
4 | 5 |
|
@@ -33,6 +34,116 @@ impl Mismatch {
|
33 | 34 | }
|
34 | 35 | }
|
35 | 36 |
|
| 37 | +/// A single span of changed lines, with 0 or more removed lines |
| 38 | +/// and a vector of 0 or more inserted lines. |
| 39 | +#[derive(Debug, PartialEq, Eq)] |
| 40 | +pub struct ModifiedChunk { |
| 41 | + /// The first to be removed from the original text |
| 42 | + pub line_number_orig: u32, |
| 43 | + /// The number of lines which have been replaced |
| 44 | + pub lines_removed: u32, |
| 45 | + /// The new lines |
| 46 | + pub lines: Vec<String>, |
| 47 | +} |
| 48 | + |
| 49 | +/// Set of changed sections of a file. |
| 50 | +#[derive(Debug, PartialEq, Eq)] |
| 51 | +pub struct ModifiedLines { |
| 52 | + /// The set of changed chunks. |
| 53 | + pub chunks: Vec<ModifiedChunk>, |
| 54 | +} |
| 55 | + |
| 56 | +impl From<Vec<Mismatch>> for ModifiedLines { |
| 57 | + fn from(mismatches: Vec<Mismatch>) -> ModifiedLines { |
| 58 | + let chunks = mismatches.into_iter().map(|mismatch| { |
| 59 | + let lines = mismatch.lines.iter(); |
| 60 | + let num_removed = lines |
| 61 | + .filter(|line| match line { |
| 62 | + DiffLine::Resulting(_) => true, |
| 63 | + _ => false, |
| 64 | + }) |
| 65 | + .count(); |
| 66 | + |
| 67 | + let new_lines = mismatch.lines.into_iter().filter_map(|line| match line { |
| 68 | + DiffLine::Context(_) | DiffLine::Resulting(_) => None, |
| 69 | + DiffLine::Expected(str) => Some(str), |
| 70 | + }); |
| 71 | + |
| 72 | + ModifiedChunk { |
| 73 | + line_number_orig: mismatch.line_number_orig, |
| 74 | + lines_removed: num_removed as u32, |
| 75 | + lines: new_lines.collect(), |
| 76 | + } |
| 77 | + }); |
| 78 | + |
| 79 | + ModifiedLines { |
| 80 | + chunks: chunks.collect(), |
| 81 | + } |
| 82 | + } |
| 83 | +} |
| 84 | + |
| 85 | +// Converts a `Mismatch` into a serialized form, which just includes |
| 86 | +// enough information to modify the original file. |
| 87 | +// Each section starts with a line with three integers, space separated: |
| 88 | +// lineno num_removed num_added |
| 89 | +// followed by (`num_added`) lines of added text. The line numbers are |
| 90 | +// relative to the original file. |
| 91 | +impl fmt::Display for ModifiedLines { |
| 92 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 93 | + for chunk in &self.chunks { |
| 94 | + writeln!( |
| 95 | + f, |
| 96 | + "{} {} {}", |
| 97 | + chunk.line_number_orig, |
| 98 | + chunk.lines_removed, |
| 99 | + chunk.lines.iter().count() |
| 100 | + )?; |
| 101 | + |
| 102 | + for line in &chunk.lines { |
| 103 | + writeln!(f, "{}", line)?; |
| 104 | + } |
| 105 | + } |
| 106 | + |
| 107 | + Ok(()) |
| 108 | + } |
| 109 | +} |
| 110 | + |
| 111 | +// Allows to convert `Display`ed `ModifiedLines` back to the structural data. |
| 112 | +impl std::str::FromStr for ModifiedLines { |
| 113 | + type Err = (); |
| 114 | + |
| 115 | + fn from_str(s: &str) -> Result<ModifiedLines, ()> { |
| 116 | + let mut chunks = vec![]; |
| 117 | + |
| 118 | + let mut lines = s.lines(); |
| 119 | + while let Some(header) = lines.next() { |
| 120 | + let mut header = header.split_whitespace(); |
| 121 | + let (orig, rem, new_lines) = match (header.next(), header.next(), header.next()) { |
| 122 | + (Some(orig), Some(removed), Some(added)) => (orig, removed, added), |
| 123 | + _ => return Err(()), |
| 124 | + }; |
| 125 | + let (orig, rem, new_lines): (u32, u32, usize) = |
| 126 | + match (orig.parse(), rem.parse(), new_lines.parse()) { |
| 127 | + (Ok(a), Ok(b), Ok(c)) => (a, b, c), |
| 128 | + _ => return Err(()), |
| 129 | + }; |
| 130 | + let lines = lines.by_ref().take(new_lines); |
| 131 | + let lines: Vec<_> = lines.map(ToOwned::to_owned).collect(); |
| 132 | + if lines.len() != new_lines { |
| 133 | + return Err(()); |
| 134 | + } |
| 135 | + |
| 136 | + chunks.push(ModifiedChunk { |
| 137 | + line_number_orig: orig, |
| 138 | + lines_removed: rem, |
| 139 | + lines, |
| 140 | + }); |
| 141 | + } |
| 142 | + |
| 143 | + Ok(ModifiedLines { chunks }) |
| 144 | + } |
| 145 | +} |
| 146 | + |
36 | 147 | // This struct handles writing output to stdout and abstracts away the logic
|
37 | 148 | // of printing in color, if it's possible in the executing environment.
|
38 | 149 | pub struct OutputWriter {
|
@@ -174,49 +285,11 @@ where
|
174 | 285 | }
|
175 | 286 | }
|
176 | 287 |
|
177 |
| -/// Converts a `Mismatch` into a serialized form, which just includes |
178 |
| -/// enough information to modify the original file. |
179 |
| -/// Each section starts with a line with three integers, space separated: |
180 |
| -/// lineno num_removed num_added |
181 |
| -/// followed by (`num_added`) lines of added text. The line numbers are |
182 |
| -/// relative to the original file. |
183 |
| -pub fn output_modified<W>(mut out: W, diff: Vec<Mismatch>) |
184 |
| -where |
185 |
| - W: Write, |
186 |
| -{ |
187 |
| - for mismatch in diff { |
188 |
| - let (num_removed, num_added) = |
189 |
| - mismatch |
190 |
| - .lines |
191 |
| - .iter() |
192 |
| - .fold((0, 0), |(rem, add), line| match *line { |
193 |
| - DiffLine::Context(_) => panic!("No Context expected"), |
194 |
| - DiffLine::Expected(_) => (rem, add + 1), |
195 |
| - DiffLine::Resulting(_) => (rem + 1, add), |
196 |
| - }); |
197 |
| - // Write a header with enough information to separate the modified lines. |
198 |
| - writeln!( |
199 |
| - out, |
200 |
| - "{} {} {}", |
201 |
| - mismatch.line_number_orig, num_removed, num_added |
202 |
| - ) |
203 |
| - .unwrap(); |
204 |
| - |
205 |
| - for line in mismatch.lines { |
206 |
| - match line { |
207 |
| - DiffLine::Context(_) | DiffLine::Resulting(_) => (), |
208 |
| - DiffLine::Expected(ref str) => { |
209 |
| - writeln!(out, "{}", str).unwrap(); |
210 |
| - } |
211 |
| - } |
212 |
| - } |
213 |
| - } |
214 |
| -} |
215 |
| - |
216 | 288 | #[cfg(test)]
|
217 | 289 | mod test {
|
218 | 290 | use super::DiffLine::*;
|
219 | 291 | use super::{make_diff, Mismatch};
|
| 292 | + use super::{ModifiedChunk, ModifiedLines}; |
220 | 293 |
|
221 | 294 | #[test]
|
222 | 295 | fn diff_simple() {
|
@@ -298,4 +371,35 @@ mod test {
|
298 | 371 | }]
|
299 | 372 | );
|
300 | 373 | }
|
| 374 | + |
| 375 | + #[test] |
| 376 | + fn modified_lines_from_str() { |
| 377 | + use std::str::FromStr; |
| 378 | + |
| 379 | + let src = "1 6 2\nfn some() {}\nfn main() {}\n25 3 1\n struct Test {}"; |
| 380 | + let lines = ModifiedLines::from_str(src).unwrap(); |
| 381 | + assert_eq!( |
| 382 | + lines, |
| 383 | + ModifiedLines { |
| 384 | + chunks: vec![ |
| 385 | + ModifiedChunk { |
| 386 | + line_number_orig: 1, |
| 387 | + lines_removed: 6, |
| 388 | + lines: vec!["fn some() {}".to_owned(), "fn main() {}".to_owned(),] |
| 389 | + }, |
| 390 | + ModifiedChunk { |
| 391 | + line_number_orig: 25, |
| 392 | + lines_removed: 3, |
| 393 | + lines: vec![" struct Test {}".to_owned()] |
| 394 | + } |
| 395 | + ] |
| 396 | + } |
| 397 | + ); |
| 398 | + |
| 399 | + let src = "1 5 3"; |
| 400 | + assert_eq!(ModifiedLines::from_str(src), Err(())); |
| 401 | + |
| 402 | + let src = "1 5 3\na\nb"; |
| 403 | + assert_eq!(ModifiedLines::from_str(src), Err(())); |
| 404 | + } |
301 | 405 | }
|
0 commit comments