Skip to content

Commit 8d65c2b

Browse files
committed
Implement -t/--expand-tabs option
1 parent a304ac0 commit 8d65c2b

File tree

10 files changed

+255
-34
lines changed

10 files changed

+255
-34
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ path = "src/main.rs"
1717
[dependencies]
1818
diff = "0.1.10"
1919
same-file = "1.0.6"
20+
unicode-width = "0.1.11"
2021

2122
[dev-dependencies]
2223
pretty_assertions = "1"

src/context_diff.rs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
use std::collections::VecDeque;
77
use std::io::Write;
88

9+
use crate::utils::do_write_line;
10+
911
#[derive(Debug, PartialEq)]
1012
pub enum DiffLine {
1113
Context(Vec<u8>),
@@ -270,6 +272,7 @@ pub fn diff(
270272
actual_filename: &str,
271273
context_size: usize,
272274
stop_early: bool,
275+
expand_tabs: bool,
273276
) -> Vec<u8> {
274277
let mut output = format!("*** {expected_filename}\t\n--- {actual_filename}\t\n").into_bytes();
275278
let diff_results = make_diff(expected, actual, context_size, stop_early);
@@ -314,17 +317,20 @@ pub fn diff(
314317
match line {
315318
DiffLine::Context(e) => {
316319
write!(output, " ").expect("write to Vec is infallible");
317-
output.write_all(&e).expect("write to Vec is infallible");
320+
do_write_line(&mut output, &e, expand_tabs)
321+
.expect("write to Vec is infallible");
318322
writeln!(output).unwrap();
319323
}
320324
DiffLine::Change(e) => {
321325
write!(output, "! ").expect("write to Vec is infallible");
322-
output.write_all(&e).expect("write to Vec is infallible");
326+
do_write_line(&mut output, &e, expand_tabs)
327+
.expect("write to Vec is infallible");
323328
writeln!(output).unwrap();
324329
}
325330
DiffLine::Add(e) => {
326331
write!(output, "- ").expect("write to Vec is infallible");
327-
output.write_all(&e).expect("write to Vec is infallible");
332+
do_write_line(&mut output, &e, expand_tabs)
333+
.expect("write to Vec is infallible");
328334
writeln!(output).unwrap();
329335
}
330336
}
@@ -341,17 +347,20 @@ pub fn diff(
341347
match line {
342348
DiffLine::Context(e) => {
343349
write!(output, " ").expect("write to Vec is infallible");
344-
output.write_all(&e).expect("write to Vec is infallible");
350+
do_write_line(&mut output, &e, expand_tabs)
351+
.expect("write to Vec is infallible");
345352
writeln!(output).unwrap();
346353
}
347354
DiffLine::Change(e) => {
348355
write!(output, "! ").expect("write to Vec is infallible");
349-
output.write_all(&e).expect("write to Vec is infallible");
356+
do_write_line(&mut output, &e, expand_tabs)
357+
.expect("write to Vec is infallible");
350358
writeln!(output).unwrap();
351359
}
352360
DiffLine::Add(e) => {
353361
write!(output, "+ ").expect("write to Vec is infallible");
354-
output.write_all(&e).expect("write to Vec is infallible");
362+
do_write_line(&mut output, &e, expand_tabs)
363+
.expect("write to Vec is infallible");
355364
writeln!(output).unwrap();
356365
}
357366
}
@@ -424,6 +433,7 @@ mod tests {
424433
&format!("{target}/alef"),
425434
2,
426435
false,
436+
false,
427437
);
428438
File::create(&format!("{target}/ab.diff"))
429439
.unwrap()
@@ -503,6 +513,7 @@ mod tests {
503513
&format!("{target}/alef_"),
504514
2,
505515
false,
516+
false,
506517
);
507518
File::create(&format!("{target}/ab_.diff"))
508519
.unwrap()
@@ -585,6 +596,7 @@ mod tests {
585596
&format!("{target}/alefx"),
586597
2,
587598
false,
599+
false,
588600
);
589601
File::create(&format!("{target}/abx.diff"))
590602
.unwrap()
@@ -670,6 +682,7 @@ mod tests {
670682
&format!("{target}/alefr"),
671683
2,
672684
false,
685+
false,
673686
);
674687
File::create(&format!("{target}/abr.diff"))
675688
.unwrap()
@@ -715,6 +728,7 @@ mod tests {
715728
to_filename,
716729
context_size,
717730
false,
731+
false,
718732
);
719733
let expected_full = [
720734
"*** foo\t",
@@ -740,6 +754,7 @@ mod tests {
740754
to_filename,
741755
context_size,
742756
true,
757+
false,
743758
);
744759
let expected_brief = ["*** foo\t", "--- bar\t", ""].join("\n");
745760
assert_eq!(diff_brief, expected_brief.as_bytes());
@@ -751,6 +766,7 @@ mod tests {
751766
to_filename,
752767
context_size,
753768
false,
769+
false,
754770
);
755771
assert!(nodiff_full.is_empty());
756772

@@ -761,6 +777,7 @@ mod tests {
761777
to_filename,
762778
context_size,
763779
true,
780+
false,
764781
);
765782
assert!(nodiff_brief.is_empty());
766783
}

src/ed_diff.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
use std::io::Write;
77

8+
use crate::utils::do_write_line;
9+
810
#[derive(Debug, PartialEq)]
911
struct Mismatch {
1012
pub line_number_expected: usize,
@@ -107,7 +109,12 @@ fn make_diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Result<Vec<Mis
107109
Ok(results)
108110
}
109111

110-
pub fn diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Result<Vec<u8>, DiffError> {
112+
pub fn diff(
113+
expected: &[u8],
114+
actual: &[u8],
115+
stop_early: bool,
116+
expand_tabs: bool,
117+
) -> Result<Vec<u8>, DiffError> {
111118
let mut output = Vec::new();
112119
let diff_results = make_diff(expected, actual, stop_early)?;
113120
if stop_early && !diff_results.is_empty() {
@@ -145,7 +152,7 @@ pub fn diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Result<Vec<u8>,
145152
if actual == b"." {
146153
writeln!(&mut output, "..\n.\ns/.//\na").unwrap();
147154
} else {
148-
output.write_all(actual).unwrap();
155+
do_write_line(&mut output, actual, expand_tabs).unwrap();
149156
writeln!(&mut output).unwrap();
150157
}
151158
}
@@ -160,7 +167,7 @@ mod tests {
160167
use super::*;
161168
use pretty_assertions::assert_eq;
162169
pub fn diff_w(expected: &[u8], actual: &[u8], filename: &str) -> Result<Vec<u8>, DiffError> {
163-
let mut output = diff(expected, actual, false)?;
170+
let mut output = diff(expected, actual, false, false)?;
164171
writeln!(&mut output, "w {filename}").unwrap();
165172
Ok(output)
166173
}
@@ -169,7 +176,7 @@ mod tests {
169176
fn test_basic() {
170177
let from = b"a\n";
171178
let to = b"b\n";
172-
let diff = diff(from, to, false).unwrap();
179+
let diff = diff(from, to, false, false).unwrap();
173180
let expected = ["1c", "b", ".", ""].join("\n");
174181
assert_eq!(diff, expected.as_bytes());
175182
}
@@ -404,18 +411,18 @@ mod tests {
404411
let from = ["a", "b", "c", ""].join("\n");
405412
let to = ["a", "d", "c", ""].join("\n");
406413

407-
let diff_full = diff(from.as_bytes(), to.as_bytes(), false).unwrap();
414+
let diff_full = diff(from.as_bytes(), to.as_bytes(), false, false).unwrap();
408415
let expected_full = ["2c", "d", ".", ""].join("\n");
409416
assert_eq!(diff_full, expected_full.as_bytes());
410417

411-
let diff_brief = diff(from.as_bytes(), to.as_bytes(), true).unwrap();
418+
let diff_brief = diff(from.as_bytes(), to.as_bytes(), true, false).unwrap();
412419
let expected_brief = "\0".as_bytes();
413420
assert_eq!(diff_brief, expected_brief);
414421

415-
let nodiff_full = diff(from.as_bytes(), from.as_bytes(), false).unwrap();
422+
let nodiff_full = diff(from.as_bytes(), from.as_bytes(), false, false).unwrap();
416423
assert!(nodiff_full.is_empty());
417424

418-
let nodiff_brief = diff(from.as_bytes(), from.as_bytes(), true).unwrap();
425+
let nodiff_brief = diff(from.as_bytes(), from.as_bytes(), true, false).unwrap();
419426
assert!(nodiff_brief.is_empty());
420427
}
421428
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod context_diff;
22
pub mod ed_diff;
33
pub mod normal_diff;
44
pub mod unified_diff;
5+
pub mod utils;
56

67
// Re-export the public functions/types you need
78
pub use context_diff::diff as context_diff;

src/main.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ mod ed_diff;
1515
mod normal_diff;
1616
mod params;
1717
mod unified_diff;
18+
mod utils;
1819

1920
// Exit codes are documented at
2021
// https://www.gnu.org/software/diffutils/manual/html_node/Invoking-diff.html.
@@ -30,6 +31,7 @@ fn main() -> ExitCode {
3031
format,
3132
report_identical_files,
3233
brief,
34+
expand_tabs,
3335
} = parse_params(opts).unwrap_or_else(|error| {
3436
eprintln!("{error}");
3537
exit(2);
@@ -65,14 +67,15 @@ fn main() -> ExitCode {
6567
};
6668
// run diff
6769
let result: Vec<u8> = match format {
68-
Format::Normal => normal_diff::diff(&from_content, &to_content, brief),
70+
Format::Normal => normal_diff::diff(&from_content, &to_content, brief, expand_tabs),
6971
Format::Unified => unified_diff::diff(
7072
&from_content,
7173
&from.to_string_lossy(),
7274
&to_content,
7375
&to.to_string_lossy(),
7476
context_count,
7577
brief,
78+
expand_tabs,
7679
),
7780
Format::Context => context_diff::diff(
7881
&from_content,
@@ -81,11 +84,14 @@ fn main() -> ExitCode {
8184
&to.to_string_lossy(),
8285
context_count,
8386
brief,
87+
expand_tabs,
8488
),
85-
Format::Ed => ed_diff::diff(&from_content, &to_content, brief).unwrap_or_else(|error| {
86-
eprintln!("{error}");
87-
exit(2);
88-
}),
89+
Format::Ed => {
90+
ed_diff::diff(&from_content, &to_content, brief, expand_tabs).unwrap_or_else(|error| {
91+
eprintln!("{error}");
92+
exit(2);
93+
})
94+
}
8995
};
9096
if brief && !result.is_empty() {
9197
println!(

src/normal_diff.rs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
use std::io::Write;
77

8+
use crate::utils::do_write_line;
9+
810
#[derive(Debug, PartialEq)]
911
struct Mismatch {
1012
pub line_number_expected: usize,
@@ -114,7 +116,7 @@ fn make_diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Vec<Mismatch>
114116
}
115117

116118
#[must_use]
117-
pub fn diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Vec<u8> {
119+
pub fn diff(expected: &[u8], actual: &[u8], stop_early: bool, expand_tabs: bool) -> Vec<u8> {
118120
// See https://www.gnu.org/software/diffutils/manual/html_node/Detailed-Normal.html
119121
// for details on the syntax of the normal format.
120122
let mut output = Vec::new();
@@ -188,7 +190,7 @@ pub fn diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Vec<u8> {
188190
}
189191
for expected in &result.expected {
190192
write!(&mut output, "< ").unwrap();
191-
output.write_all(expected).unwrap();
193+
do_write_line(&mut output, expected, expand_tabs).unwrap();
192194
writeln!(&mut output).unwrap();
193195
}
194196
if result.expected_missing_nl {
@@ -199,7 +201,7 @@ pub fn diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Vec<u8> {
199201
}
200202
for actual in &result.actual {
201203
write!(&mut output, "> ").unwrap();
202-
output.write_all(actual).unwrap();
204+
do_write_line(&mut output, actual, expand_tabs).unwrap();
203205
writeln!(&mut output).unwrap();
204206
}
205207
if result.actual_missing_nl {
@@ -220,7 +222,7 @@ mod tests {
220222
a.write_all(b"a\n").unwrap();
221223
let mut b = Vec::new();
222224
b.write_all(b"b\n").unwrap();
223-
let diff = diff(&a, &b, false);
225+
let diff = diff(&a, &b, false, false);
224226
let expected = b"1c1\n< a\n---\n> b\n".to_vec();
225227
assert_eq!(diff, expected);
226228
}
@@ -273,7 +275,7 @@ mod tests {
273275
}
274276
// This test diff is intentionally reversed.
275277
// We want it to turn the alef into bet.
276-
let diff = diff(&alef, &bet, false);
278+
let diff = diff(&alef, &bet, false, false);
277279
File::create(&format!("{target}/ab.diff"))
278280
.unwrap()
279281
.write_all(&diff)
@@ -365,7 +367,7 @@ mod tests {
365367
}
366368
// This test diff is intentionally reversed.
367369
// We want it to turn the alef into bet.
368-
let diff = diff(&alef, &bet, false);
370+
let diff = diff(&alef, &bet, false, false);
369371
File::create(&format!("{target}/abn.diff"))
370372
.unwrap()
371373
.write_all(&diff)
@@ -439,7 +441,7 @@ mod tests {
439441
}
440442
// This test diff is intentionally reversed.
441443
// We want it to turn the alef into bet.
442-
let diff = diff(&alef, &bet, false);
444+
let diff = diff(&alef, &bet, false, false);
443445
File::create(&format!("{target}/ab_.diff"))
444446
.unwrap()
445447
.write_all(&diff)
@@ -517,7 +519,7 @@ mod tests {
517519
}
518520
// This test diff is intentionally reversed.
519521
// We want it to turn the alef into bet.
520-
let diff = diff(&alef, &bet, false);
522+
let diff = diff(&alef, &bet, false, false);
521523
File::create(&format!("{target}/abr.diff"))
522524
.unwrap()
523525
.write_all(&diff)
@@ -552,18 +554,18 @@ mod tests {
552554
let from = ["a", "b", "c"].join("\n");
553555
let to = ["a", "d", "c"].join("\n");
554556

555-
let diff_full = diff(from.as_bytes(), to.as_bytes(), false);
557+
let diff_full = diff(from.as_bytes(), to.as_bytes(), false, false);
556558
let expected_full = ["2c2", "< b", "---", "> d", ""].join("\n");
557559
assert_eq!(diff_full, expected_full.as_bytes());
558560

559-
let diff_brief = diff(from.as_bytes(), to.as_bytes(), true);
561+
let diff_brief = diff(from.as_bytes(), to.as_bytes(), true, false);
560562
let expected_brief = "\0".as_bytes();
561563
assert_eq!(diff_brief, expected_brief);
562564

563-
let nodiff_full = diff(from.as_bytes(), from.as_bytes(), false);
565+
let nodiff_full = diff(from.as_bytes(), from.as_bytes(), false, false);
564566
assert!(nodiff_full.is_empty());
565567

566-
let nodiff_brief = diff(from.as_bytes(), from.as_bytes(), true);
568+
let nodiff_brief = diff(from.as_bytes(), from.as_bytes(), true, false);
567569
assert!(nodiff_brief.is_empty());
568570
}
569571
}

0 commit comments

Comments
 (0)