Skip to content

Commit 4ed7ea1

Browse files
authored
Implement -q/--brief option (fixes #19) (#20)
* Implement -q/--brief option * Optimization: stop analyzing the files as soon as there are any differences * Unit tests for the stop_early parameter * Simplify checks
1 parent 62e10c6 commit 4ed7ea1

File tree

7 files changed

+394
-40
lines changed

7 files changed

+394
-40
lines changed

src/context_diff.rs

Lines changed: 113 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,12 @@ impl Mismatch {
4141
}
4242

4343
// Produces a diff between the expected output and actual output.
44-
fn make_diff(expected: &[u8], actual: &[u8], context_size: usize) -> Vec<Mismatch> {
44+
fn make_diff(
45+
expected: &[u8],
46+
actual: &[u8],
47+
context_size: usize,
48+
stop_early: bool,
49+
) -> Vec<Mismatch> {
4550
let mut line_number_expected = 1;
4651
let mut line_number_actual = 1;
4752
let mut context_queue: VecDeque<&[u8]> = VecDeque::with_capacity(context_size);
@@ -191,6 +196,10 @@ fn make_diff(expected: &[u8], actual: &[u8], context_size: usize) -> Vec<Mismatc
191196
line_number_actual += 1;
192197
}
193198
}
199+
if stop_early && !results.is_empty() {
200+
// Optimization: stop analyzing the files as soon as there are any differences
201+
return results;
202+
}
194203
}
195204

196205
results.push(mismatch);
@@ -260,12 +269,16 @@ pub fn diff(
260269
actual: &[u8],
261270
actual_filename: &str,
262271
context_size: usize,
272+
stop_early: bool,
263273
) -> Vec<u8> {
264274
let mut output = format!("*** {expected_filename}\t\n--- {actual_filename}\t\n").into_bytes();
265-
let diff_results = make_diff(expected, actual, context_size);
275+
let diff_results = make_diff(expected, actual, context_size, stop_early);
266276
if diff_results.is_empty() {
267277
return Vec::new();
268-
};
278+
}
279+
if stop_early {
280+
return output;
281+
}
269282
for result in diff_results {
270283
let mut line_number_expected = result.line_number_expected;
271284
let mut line_number_actual = result.line_number_actual;
@@ -404,8 +417,14 @@ mod tests {
404417
}
405418
// This test diff is intentionally reversed.
406419
// We want it to turn the alef into bet.
407-
let diff =
408-
diff(&alef, "a/alef", &bet, &format!("{target}/alef"), 2);
420+
let diff = diff(
421+
&alef,
422+
"a/alef",
423+
&bet,
424+
&format!("{target}/alef"),
425+
2,
426+
false,
427+
);
409428
File::create(&format!("{target}/ab.diff"))
410429
.unwrap()
411430
.write_all(&diff)
@@ -477,8 +496,14 @@ mod tests {
477496
}
478497
// This test diff is intentionally reversed.
479498
// We want it to turn the alef into bet.
480-
let diff =
481-
diff(&alef, "a/alef_", &bet, &format!("{target}/alef_"), 2);
499+
let diff = diff(
500+
&alef,
501+
"a/alef_",
502+
&bet,
503+
&format!("{target}/alef_"),
504+
2,
505+
false,
506+
);
482507
File::create(&format!("{target}/ab_.diff"))
483508
.unwrap()
484509
.write_all(&diff)
@@ -553,8 +578,14 @@ mod tests {
553578
};
554579
// This test diff is intentionally reversed.
555580
// We want it to turn the alef into bet.
556-
let diff =
557-
diff(&alef, "a/alefx", &bet, &format!("{target}/alefx"), 2);
581+
let diff = diff(
582+
&alef,
583+
"a/alefx",
584+
&bet,
585+
&format!("{target}/alefx"),
586+
2,
587+
false,
588+
);
558589
File::create(&format!("{target}/abx.diff"))
559590
.unwrap()
560591
.write_all(&diff)
@@ -632,8 +663,14 @@ mod tests {
632663
}
633664
// This test diff is intentionally reversed.
634665
// We want it to turn the alef into bet.
635-
let diff =
636-
diff(&alef, "a/alefr", &bet, &format!("{target}/alefr"), 2);
666+
let diff = diff(
667+
&alef,
668+
"a/alefr",
669+
&bet,
670+
&format!("{target}/alefr"),
671+
2,
672+
false,
673+
);
637674
File::create(&format!("{target}/abr.diff"))
638675
.unwrap()
639676
.write_all(&diff)
@@ -662,4 +699,69 @@ mod tests {
662699
}
663700
}
664701
}
702+
703+
#[test]
704+
fn test_stop_early() {
705+
let from_filename = "foo";
706+
let from = vec!["a", "b", "c", ""].join("\n");
707+
let to_filename = "bar";
708+
let to = vec!["a", "d", "c", ""].join("\n");
709+
let context_size: usize = 3;
710+
711+
let diff_full = diff(
712+
from.as_bytes(),
713+
from_filename,
714+
to.as_bytes(),
715+
to_filename,
716+
context_size,
717+
false,
718+
);
719+
let expected_full = vec![
720+
"*** foo\t",
721+
"--- bar\t",
722+
"***************",
723+
"*** 1,3 ****",
724+
" a",
725+
"! b",
726+
" c",
727+
"--- 1,3 ----",
728+
" a",
729+
"! d",
730+
" c",
731+
"",
732+
]
733+
.join("\n");
734+
assert_eq!(diff_full, expected_full.as_bytes());
735+
736+
let diff_brief = diff(
737+
from.as_bytes(),
738+
from_filename,
739+
to.as_bytes(),
740+
to_filename,
741+
context_size,
742+
true,
743+
);
744+
let expected_brief = vec!["*** foo\t", "--- bar\t", ""].join("\n");
745+
assert_eq!(diff_brief, expected_brief.as_bytes());
746+
747+
let nodiff_full = diff(
748+
from.as_bytes(),
749+
from_filename,
750+
from.as_bytes(),
751+
to_filename,
752+
context_size,
753+
false,
754+
);
755+
assert!(nodiff_full.is_empty());
756+
757+
let nodiff_brief = diff(
758+
from.as_bytes(),
759+
from_filename,
760+
from.as_bytes(),
761+
to_filename,
762+
context_size,
763+
true,
764+
);
765+
assert!(nodiff_brief.is_empty());
766+
}
665767
}

src/ed_diff.rs

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ impl Mismatch {
4242
}
4343

4444
// Produces a diff between the expected output and actual output.
45-
fn make_diff(expected: &[u8], actual: &[u8]) -> Result<Vec<Mismatch>, DiffError> {
45+
fn make_diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Result<Vec<Mismatch>, DiffError> {
4646
let mut line_number_expected = 1;
4747
let mut line_number_actual = 1;
4848
let mut results = Vec::new();
@@ -94,6 +94,10 @@ fn make_diff(expected: &[u8], actual: &[u8]) -> Result<Vec<Mismatch>, DiffError>
9494
}
9595
}
9696
}
97+
if stop_early && !results.is_empty() {
98+
// Optimization: stop analyzing the files as soon as there are any differences
99+
return Ok(results);
100+
}
97101
}
98102

99103
if !mismatch.actual.is_empty() || !mismatch.expected.is_empty() {
@@ -103,9 +107,13 @@ fn make_diff(expected: &[u8], actual: &[u8]) -> Result<Vec<Mismatch>, DiffError>
103107
Ok(results)
104108
}
105109

106-
pub fn diff(expected: &[u8], actual: &[u8]) -> Result<Vec<u8>, DiffError> {
110+
pub fn diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Result<Vec<u8>, DiffError> {
107111
let mut output = Vec::new();
108-
let diff_results = make_diff(expected, actual)?;
112+
let diff_results = make_diff(expected, actual, stop_early)?;
113+
if stop_early && !diff_results.is_empty() {
114+
write!(&mut output, "\0").unwrap();
115+
return Ok(output);
116+
}
109117
let mut lines_offset = 0;
110118
for result in diff_results {
111119
let line_number_expected: isize = result.line_number_expected as isize + lines_offset;
@@ -152,7 +160,7 @@ mod tests {
152160
use super::*;
153161
use pretty_assertions::assert_eq;
154162
pub fn diff_w(expected: &[u8], actual: &[u8], filename: &str) -> Result<Vec<u8>, DiffError> {
155-
let mut output = diff(expected, actual)?;
163+
let mut output = diff(expected, actual, false)?;
156164
writeln!(&mut output, "w {filename}").unwrap();
157165
Ok(output)
158166
}
@@ -161,7 +169,7 @@ mod tests {
161169
fn test_basic() {
162170
let from = b"a\n";
163171
let to = b"b\n";
164-
let diff = diff(from, to).unwrap();
172+
let diff = diff(from, to, false).unwrap();
165173
let expected = vec!["1c", "b", ".", ""].join("\n");
166174
assert_eq!(diff, expected.as_bytes());
167175
}
@@ -390,4 +398,24 @@ mod tests {
390398
}
391399
}
392400
}
401+
402+
#[test]
403+
fn test_stop_early() {
404+
let from = vec!["a", "b", "c", ""].join("\n");
405+
let to = vec!["a", "d", "c", ""].join("\n");
406+
407+
let diff_full = diff(from.as_bytes(), to.as_bytes(), false).unwrap();
408+
let expected_full = vec!["2c", "d", ".", ""].join("\n");
409+
assert_eq!(diff_full, expected_full.as_bytes());
410+
411+
let diff_brief = diff(from.as_bytes(), to.as_bytes(), true).unwrap();
412+
let expected_brief = "\0".as_bytes();
413+
assert_eq!(diff_brief, expected_brief);
414+
415+
let nodiff_full = diff(from.as_bytes(), from.as_bytes(), false).unwrap();
416+
assert!(nodiff_full.is_empty());
417+
418+
let nodiff_brief = diff(from.as_bytes(), from.as_bytes(), true).unwrap();
419+
assert!(nodiff_brief.is_empty());
420+
}
393421
}

src/main.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ fn main() -> ExitCode {
2929
context_count,
3030
format,
3131
report_identical_files,
32+
brief,
3233
} = parse_params(opts).unwrap_or_else(|error| {
3334
eprintln!("{error}");
3435
exit(2);
@@ -64,27 +65,37 @@ fn main() -> ExitCode {
6465
};
6566
// run diff
6667
let result: Vec<u8> = match format {
67-
Format::Normal => normal_diff::diff(&from_content, &to_content),
68+
Format::Normal => normal_diff::diff(&from_content, &to_content, brief),
6869
Format::Unified => unified_diff::diff(
6970
&from_content,
7071
&from.to_string_lossy(),
7172
&to_content,
7273
&to.to_string_lossy(),
7374
context_count,
75+
brief,
7476
),
7577
Format::Context => context_diff::diff(
7678
&from_content,
7779
&from.to_string_lossy(),
7880
&to_content,
7981
&to.to_string_lossy(),
8082
context_count,
83+
brief,
8184
),
82-
Format::Ed => ed_diff::diff(&from_content, &to_content).unwrap_or_else(|error| {
85+
Format::Ed => ed_diff::diff(&from_content, &to_content, brief).unwrap_or_else(|error| {
8386
eprintln!("{error}");
8487
exit(2);
8588
}),
8689
};
87-
io::stdout().write_all(&result).unwrap();
90+
if brief && !result.is_empty() {
91+
println!(
92+
"Files {} and {} differ",
93+
from.to_string_lossy(),
94+
to.to_string_lossy()
95+
);
96+
} else {
97+
io::stdout().write_all(&result).unwrap();
98+
}
8899
if result.is_empty() {
89100
maybe_report_identical_files();
90101
ExitCode::SUCCESS

0 commit comments

Comments
 (0)