mirror of
https://github.com/uutils/diffutils.git
synced 2026-06-10 15:48:59 -07:00
Add context and normal diff modes
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
/target
|
||||
/lib/normal-diff/target
|
||||
/lib/context-diff/target
|
||||
/lib/unified-diff/target
|
||||
Cargo.lock
|
||||
*.swp
|
||||
|
||||
+6
-14
@@ -1,15 +1,7 @@
|
||||
[package]
|
||||
name = "unified-diff"
|
||||
version = "0.2.1"
|
||||
authors = [
|
||||
"Michael Howell <michael@notriddle.com>",
|
||||
"The Rust Project Developers"
|
||||
[workspace]
|
||||
members = [
|
||||
"lib/unified-diff",
|
||||
"lib/context-diff",
|
||||
"lib/normal-diff",
|
||||
"bin/diff",
|
||||
]
|
||||
edition = "2018"
|
||||
description = "An implementation of the GNU unified diff format"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/notriddle/rust-unified-diff"
|
||||
exclude = [ "fuzz" ]
|
||||
|
||||
[dependencies]
|
||||
diff = "0.1.10"
|
||||
|
||||
@@ -1,17 +1,34 @@
|
||||
A GNU unified diff generator. Oracle tested against GNU patch 2.7.6
|
||||
A package (currently just `diff`, but eventually more) of programs related to finding differences between files.
|
||||
|
||||
Based on the incomplete diff generator in https://github.com/rust-lang/rust/blob/master/src/tools/compiletest/src/runtest.rs,
|
||||
but it implements a different format.
|
||||
Based on the incomplete diff generator in https://github.com/rust-lang/rust/blob/master/src/tools/compiletest/src/runtest.rs, and made to be compatible with GNU's diff and patch tools.
|
||||
|
||||
```
|
||||
~/unified-diff$ cargo run Cargo.lock Cargo.toml
|
||||
~/diffutils$ cargo run -- -u3 Cargo.lock Cargo.toml
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
|
||||
Running `target/debug/unified-diff Cargo.lock Cargo.toml`
|
||||
Running `target/debug/diff -u3 Cargo.lock Cargo.toml`
|
||||
--- Cargo.lock
|
||||
+++ Cargo.toml
|
||||
@@ -1,14 +1,14 @@
|
||||
@@ -1,39 +1,7 @@
|
||||
-# This file is automatically @generated by Cargo.
|
||||
-# It is not intended for manual editing.
|
||||
-version = 3
|
||||
-
|
||||
-[[package]]
|
||||
-name = "context-diff"
|
||||
-version = "0.1.0"
|
||||
-dependencies = [
|
||||
- "diff 0.1.12",
|
||||
-]
|
||||
-
|
||||
-[[package]]
|
||||
-name = "diff"
|
||||
-version = "0.1.0"
|
||||
-dependencies = [
|
||||
- "context-diff",
|
||||
- "normal-diff",
|
||||
- "unified-diff",
|
||||
-]
|
||||
-
|
||||
-[[package]]
|
||||
-name = "diff"
|
||||
-version = "0.1.12"
|
||||
@@ -19,23 +36,23 @@ but it implements a different format.
|
||||
-checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
|
||||
-
|
||||
-[[package]]
|
||||
+[package]
|
||||
name = "unified-diff"
|
||||
version = "0.1.0"
|
||||
-name = "normal-diff"
|
||||
-version = "0.1.0"
|
||||
-dependencies = [
|
||||
- "diff",
|
||||
+authors = [
|
||||
+ "Michael Howell <michael@notriddle.com>",
|
||||
+ "The Rust Project Developers"
|
||||
- "diff 0.1.12",
|
||||
-]
|
||||
-
|
||||
-[[package]]
|
||||
-name = "unified-diff"
|
||||
-version = "0.3.0"
|
||||
-dependencies = [
|
||||
- "diff 0.1.12",
|
||||
+[workspace]
|
||||
+members = [
|
||||
+ "lib/unified-diff",
|
||||
+ "lib/context-diff",
|
||||
+ "lib/normal-diff",
|
||||
+ "bin/diff",
|
||||
]
|
||||
+edition = "2018"
|
||||
+
|
||||
+[[bin]]
|
||||
+name = "unified-diff"
|
||||
+
|
||||
+[dependencies]
|
||||
+diff = "0.1.10"
|
||||
~/unified-diff$ rustup override set nightly
|
||||
~/unified-diff$ cargo fuzz run fuzz_patch
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "diff"
|
||||
version = "0.1.0"
|
||||
authors = ["Michael Howell <michael@notriddle.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
context-diff = { path = "../../lib/context-diff" }
|
||||
normal-diff = { path = "../../lib/normal-diff" }
|
||||
unified-diff = { path = "../../lib/unified-diff" }
|
||||
@@ -0,0 +1,50 @@
|
||||
use crate::params::*;
|
||||
use std::env;
|
||||
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
|
||||
mod params;
|
||||
|
||||
fn main() -> Result<(), String> {
|
||||
let opts = env::args_os();
|
||||
let Params {
|
||||
from,
|
||||
to,
|
||||
context_count,
|
||||
format,
|
||||
} = parse_params(opts)?;
|
||||
// read files
|
||||
let from_content = match fs::read(&from) {
|
||||
Ok(from_content) => from_content,
|
||||
Err(e) => {
|
||||
return Err(format!("Failed to read from-file: {}", e));
|
||||
}
|
||||
};
|
||||
let to_content = match fs::read(&to) {
|
||||
Ok(to_content) => to_content,
|
||||
Err(e) => {
|
||||
return Err(format!("Failed to read from-file: {}", e));
|
||||
}
|
||||
};
|
||||
// run diff
|
||||
let result: Vec<u8> = match format {
|
||||
Format::Normal => normal_diff::diff(&from_content, &to_content),
|
||||
Format::Unified => unified_diff::diff(
|
||||
&from_content,
|
||||
&from.to_string_lossy(),
|
||||
&to_content,
|
||||
&to.to_string_lossy(),
|
||||
context_count,
|
||||
),
|
||||
Format::Context => context_diff::diff(
|
||||
&from_content,
|
||||
&from.to_string_lossy(),
|
||||
&to_content,
|
||||
&to.to_string_lossy(),
|
||||
context_count,
|
||||
),
|
||||
};
|
||||
io::stdout().write_all(&result).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
use std::ffi::{OsStr, OsString};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Format {
|
||||
Normal,
|
||||
Unified,
|
||||
Context,
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn osstr_bytes(osstr: &OsStr) -> &[u8] {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
osstr.as_bytes()
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn osstr_bytes(osstr: &OsStr) -> Vec<u8> {
|
||||
osstr.to_string_lossy().bytes().collect()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Params {
|
||||
pub from: OsString,
|
||||
pub to: OsString,
|
||||
pub format: Format,
|
||||
pub context_count: usize,
|
||||
}
|
||||
|
||||
pub fn parse_params<I: IntoIterator<Item = OsString>>(opts: I) -> Result<Params, String> {
|
||||
let mut opts = opts.into_iter();
|
||||
// parse CLI
|
||||
let exe = match opts.next() {
|
||||
Some(from) => from,
|
||||
None => {
|
||||
return Err(format!("Usage: <exe> <from> <to>"));
|
||||
}
|
||||
};
|
||||
let mut from = None;
|
||||
let mut to = None;
|
||||
let mut format = None;
|
||||
let mut context_count = 3;
|
||||
while let Some(param) = opts.next() {
|
||||
if param == "--" {
|
||||
break;
|
||||
}
|
||||
if param == "-" {
|
||||
if from.is_none() {
|
||||
from = Some(OsString::from("/dev/stdin"));
|
||||
} else if to.is_none() {
|
||||
to = Some(OsString::from("/dev/stdin"));
|
||||
} else {
|
||||
return Err(format!("Usage: {} <from> <to>", exe.to_string_lossy()));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let p = osstr_bytes(¶m);
|
||||
if p.get(0) == Some(&b'-') && p.get(1) != Some(&b'-') {
|
||||
let mut bit = p[1..].into_iter().copied().peekable();
|
||||
// Can't use a for loop because `diff -30u` is supposed to make a diff
|
||||
// with 30 lines of context.
|
||||
while let Some(b) = bit.next() {
|
||||
match b {
|
||||
b'0'..=b'9' => {
|
||||
context_count = (b - b'0') as usize;
|
||||
while let Some(b'0'..=b'9') = bit.peek() {
|
||||
context_count = context_count * 10;
|
||||
context_count += (bit.next().unwrap() - b'0') as usize;
|
||||
}
|
||||
}
|
||||
b'c' => {
|
||||
if format.is_some() && format != Some(Format::Context) {
|
||||
return Err(format!("Conflicting output style options"));
|
||||
}
|
||||
format = Some(Format::Context);
|
||||
}
|
||||
b'u' => {
|
||||
if format.is_some() && format != Some(Format::Unified) {
|
||||
return Err(format!("Conflicting output style options"));
|
||||
}
|
||||
format = Some(Format::Unified);
|
||||
}
|
||||
b'U' => {
|
||||
if format.is_some() && format != Some(Format::Unified) {
|
||||
return Err(format!("Conflicting output style options"));
|
||||
}
|
||||
format = Some(Format::Unified);
|
||||
let context_count_maybe = if bit.peek().is_some() {
|
||||
String::from_utf8(bit.collect::<Vec<u8>>()).ok()
|
||||
} else {
|
||||
opts.next().map(|x| x.to_string_lossy().into_owned())
|
||||
};
|
||||
if let Some(context_count_maybe) =
|
||||
context_count_maybe.and_then(|x| x.parse().ok())
|
||||
{
|
||||
context_count = context_count_maybe;
|
||||
break;
|
||||
} else {
|
||||
return Err(format!("Invalid context count"));
|
||||
}
|
||||
}
|
||||
_ => return Err(format!("Unknown option: {}", String::from_utf8_lossy(&[b]))),
|
||||
}
|
||||
}
|
||||
} else if from.is_none() {
|
||||
from = Some(param);
|
||||
} else if to.is_none() {
|
||||
to = Some(param)
|
||||
} else {
|
||||
return Err(format!("Usage: {} <from> <to>", exe.to_string_lossy()));
|
||||
}
|
||||
}
|
||||
let from = if let Some(from) = from {
|
||||
from
|
||||
} else if let Some(param) = opts.next() {
|
||||
param
|
||||
} else {
|
||||
return Err(format!("Usage: {} <from> <to>", exe.to_string_lossy()));
|
||||
};
|
||||
let to = if let Some(to) = to {
|
||||
to
|
||||
} else if let Some(param) = opts.next() {
|
||||
param
|
||||
} else {
|
||||
return Err(format!("Usage: {} <from> <to>", exe.to_string_lossy()));
|
||||
};
|
||||
let format = format.unwrap_or(Format::Normal);
|
||||
Ok(Params {
|
||||
from,
|
||||
to,
|
||||
format,
|
||||
context_count,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
fn os(s: &str) -> OsString {
|
||||
OsString::from(s)
|
||||
}
|
||||
#[test]
|
||||
fn basics() {
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
format: Format::Normal,
|
||||
context_count: 3,
|
||||
}),
|
||||
parse_params([os("diff"), os("foo"), os("bar")].iter().cloned())
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn context_count() {
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
format: Format::Unified,
|
||||
context_count: 54,
|
||||
}),
|
||||
parse_params(
|
||||
[os("diff"), os("-u54"), os("foo"), os("bar")]
|
||||
.iter()
|
||||
.cloned()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
format: Format::Unified,
|
||||
context_count: 54,
|
||||
}),
|
||||
parse_params(
|
||||
[os("diff"), os("-U54"), os("foo"), os("bar")]
|
||||
.iter()
|
||||
.cloned()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
format: Format::Unified,
|
||||
context_count: 54,
|
||||
}),
|
||||
parse_params(
|
||||
[os("diff"), os("-U"), os("54"), os("foo"), os("bar")]
|
||||
.iter()
|
||||
.cloned()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
from: os("foo"),
|
||||
to: os("bar"),
|
||||
format: Format::Context,
|
||||
context_count: 54,
|
||||
}),
|
||||
parse_params(
|
||||
[os("diff"), os("-c54"), os("foo"), os("bar")]
|
||||
.iter()
|
||||
.cloned()
|
||||
)
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn double_dash() {
|
||||
assert_eq!(
|
||||
Ok(Params {
|
||||
from: os("-g"),
|
||||
to: os("-h"),
|
||||
format: Format::Normal,
|
||||
context_count: 3,
|
||||
}),
|
||||
parse_params([os("diff"), os("--"), os("-g"), os("-h")].iter().cloned())
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn unknown_argument() {
|
||||
assert!(
|
||||
parse_params([os("diff"), os("-g"), os("foo"), os("bar")].iter().cloned()).is_err()
|
||||
);
|
||||
assert!(parse_params([os("diff"), os("-g"), os("bar")].iter().cloned()).is_err());
|
||||
assert!(parse_params([os("diff"), os("-g")].iter().cloned()).is_err());
|
||||
}
|
||||
#[test]
|
||||
fn empty() {
|
||||
assert!(parse_params([].iter().cloned()).is_err());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "context-diff"
|
||||
version = "0.1.0"
|
||||
authors = [
|
||||
"Michael Howell <michael@notriddle.com>",
|
||||
"The Rust Project Developers"
|
||||
]
|
||||
edition = "2018"
|
||||
description = "An implementation of the GNU unified diff format"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/notriddle/diffutils"
|
||||
exclude = [ "fuzz" ]
|
||||
|
||||
[dependencies]
|
||||
diff = "0.1.10"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "normal-diff"
|
||||
version = "0.1.0"
|
||||
authors = [
|
||||
"Michael Howell <michael@notriddle.com>",
|
||||
"The Rust Project Developers"
|
||||
]
|
||||
edition = "2018"
|
||||
description = "An implementation of the GNU unified diff format"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/notriddle/diffutils"
|
||||
exclude = [ "fuzz" ]
|
||||
|
||||
[dependencies]
|
||||
diff = "0.1.10"
|
||||
@@ -0,0 +1,362 @@
|
||||
use std::io::Write;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct Mismatch {
|
||||
pub line_number_expected: usize,
|
||||
pub line_number_actual: usize,
|
||||
pub expected: Vec<Vec<u8>>,
|
||||
pub actual: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl Mismatch {
|
||||
fn new(line_number_expected: usize, line_number_actual: usize) -> Mismatch {
|
||||
Mismatch {
|
||||
line_number_expected,
|
||||
line_number_actual,
|
||||
expected: Vec::new(),
|
||||
actual: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Produces a diff between the expected output and actual output.
|
||||
fn make_diff(expected: &[u8], actual: &[u8]) -> Vec<Mismatch> {
|
||||
let mut line_number_expected = 1;
|
||||
let mut line_number_actual = 1;
|
||||
let mut results = Vec::new();
|
||||
let mut mismatch = Mismatch::new(line_number_expected, line_number_actual);
|
||||
|
||||
let mut expected_lines: Vec<&[u8]> = expected.split(|&c| c == b'\n').collect();
|
||||
let mut actual_lines: Vec<&[u8]> = actual.split(|&c| c == b'\n').collect();
|
||||
|
||||
debug_assert_eq!(b"".split(|&c| c == b'\n').count(), 1);
|
||||
// ^ means that underflow here is impossible
|
||||
let _expected_lines_count = expected_lines.len() - 1;
|
||||
let _actual_lines_count = actual_lines.len() - 1;
|
||||
|
||||
if expected_lines.last() == Some(&&b""[..]) {
|
||||
expected_lines.pop();
|
||||
}
|
||||
|
||||
if actual_lines.last() == Some(&&b""[..]) {
|
||||
actual_lines.pop();
|
||||
}
|
||||
|
||||
for result in diff::slice(&expected_lines, &actual_lines) {
|
||||
match result {
|
||||
diff::Result::Left(str) => {
|
||||
if mismatch.actual.len() != 0 {
|
||||
results.push(mismatch);
|
||||
mismatch = Mismatch::new(line_number_expected, line_number_actual);
|
||||
}
|
||||
mismatch.expected.push(str.to_vec());
|
||||
line_number_expected += 1;
|
||||
}
|
||||
diff::Result::Right(str) => {
|
||||
mismatch.actual.push(str.to_vec());
|
||||
line_number_actual += 1;
|
||||
}
|
||||
diff::Result::Both(_str, _) => {
|
||||
line_number_expected += 1;
|
||||
line_number_actual += 1;
|
||||
if mismatch.actual.len() != 0 || mismatch.expected.len() != 0 {
|
||||
results.push(mismatch);
|
||||
mismatch = Mismatch::new(line_number_expected, line_number_actual);
|
||||
} else {
|
||||
mismatch.line_number_expected = line_number_expected;
|
||||
mismatch.line_number_actual = line_number_actual;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if mismatch.actual.len() != 0 || mismatch.expected.len() != 0 {
|
||||
results.push(mismatch);
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
pub fn diff(expected: &[u8], actual: &[u8]) -> Vec<u8> {
|
||||
let mut output = Vec::new();
|
||||
let diff_results = make_diff(expected, actual);
|
||||
for result in diff_results {
|
||||
let line_number_expected = result.line_number_expected;
|
||||
let line_number_actual = result.line_number_actual;
|
||||
let expected_count = result.expected.len();
|
||||
let actual_count = result.actual.len();
|
||||
match (expected_count, actual_count) {
|
||||
(0, 0) => unreachable!(),
|
||||
(0, _) => writeln!(
|
||||
&mut output,
|
||||
"{}a{},{}",
|
||||
line_number_expected - 1,
|
||||
line_number_actual,
|
||||
line_number_actual + actual_count - 1
|
||||
)
|
||||
.unwrap(),
|
||||
(_, 0) => writeln!(
|
||||
&mut output,
|
||||
"{},{}d{}",
|
||||
line_number_expected,
|
||||
expected_count + line_number_expected - 1,
|
||||
line_number_actual - 1
|
||||
)
|
||||
.unwrap(),
|
||||
_ => writeln!(
|
||||
&mut output,
|
||||
"{},{}c{},{}",
|
||||
line_number_expected,
|
||||
expected_count + line_number_expected - 1,
|
||||
line_number_actual,
|
||||
actual_count + line_number_actual - 1
|
||||
)
|
||||
.unwrap(),
|
||||
}
|
||||
for expected in &result.expected {
|
||||
write!(&mut output, "< ").unwrap();
|
||||
output.write_all(expected).unwrap();
|
||||
writeln!(&mut output, "").unwrap();
|
||||
}
|
||||
if expected_count != 0 && actual_count != 0 {
|
||||
writeln!(&mut output, "---").unwrap();
|
||||
}
|
||||
for actual in &result.actual {
|
||||
write!(&mut output, "> ").unwrap();
|
||||
output.write_all(actual).unwrap();
|
||||
writeln!(&mut output, "").unwrap();
|
||||
}
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_permutations() {
|
||||
// test all possible six-line files.
|
||||
let _ = std::fs::create_dir("target");
|
||||
for &a in &[0, 1, 2] {
|
||||
for &b in &[0, 1, 2] {
|
||||
for &c in &[0, 1, 2] {
|
||||
for &d in &[0, 1, 2] {
|
||||
for &e in &[0, 1, 2] {
|
||||
for &f in &[0, 1, 2] {
|
||||
use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
use std::process::Command;
|
||||
let mut alef = Vec::new();
|
||||
let mut bet = Vec::new();
|
||||
alef.write_all(if a == 0 { b"a\n" } else { b"b\n" })
|
||||
.unwrap();
|
||||
if a != 2 {
|
||||
bet.write_all(b"b\n").unwrap();
|
||||
}
|
||||
alef.write_all(if b == 0 { b"c\n" } else { b"d\n" })
|
||||
.unwrap();
|
||||
if b != 2 {
|
||||
bet.write_all(b"d\n").unwrap();
|
||||
}
|
||||
alef.write_all(if c == 0 { b"e\n" } else { b"f\n" })
|
||||
.unwrap();
|
||||
if c != 2 {
|
||||
bet.write_all(b"f\n").unwrap();
|
||||
}
|
||||
alef.write_all(if d == 0 { b"g\n" } else { b"h\n" })
|
||||
.unwrap();
|
||||
if d != 2 {
|
||||
bet.write_all(b"h\n").unwrap();
|
||||
}
|
||||
alef.write_all(if e == 0 { b"i\n" } else { b"j\n" })
|
||||
.unwrap();
|
||||
if e != 2 {
|
||||
bet.write_all(b"j\n").unwrap();
|
||||
}
|
||||
alef.write_all(if f == 0 { b"k\n" } else { b"l\n" })
|
||||
.unwrap();
|
||||
if f != 2 {
|
||||
bet.write_all(b"l\n").unwrap();
|
||||
}
|
||||
// This test diff is intentionally reversed.
|
||||
// We want it to turn the alef into bet.
|
||||
let diff = diff(&alef, &bet);
|
||||
File::create("target/ab.diff")
|
||||
.unwrap()
|
||||
.write_all(&diff)
|
||||
.unwrap();
|
||||
let mut fa = File::create("target/alef").unwrap();
|
||||
fa.write_all(&alef[..]).unwrap();
|
||||
let mut fb = File::create("target/bet").unwrap();
|
||||
fb.write_all(&bet[..]).unwrap();
|
||||
let _ = fa;
|
||||
let _ = fb;
|
||||
let output = Command::new("patch")
|
||||
.arg("-p0")
|
||||
.arg("target/alef")
|
||||
.stdin(File::open("target/ab.diff").unwrap())
|
||||
.output()
|
||||
.unwrap();
|
||||
if !output.status.success() {
|
||||
panic!("{:?}", output);
|
||||
}
|
||||
//println!("{}", String::from_utf8_lossy(&output.stdout));
|
||||
//println!("{}", String::from_utf8_lossy(&output.stderr));
|
||||
let alef = fs::read("target/alef").unwrap();
|
||||
assert_eq!(alef, bet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_permutations_empty_lines() {
|
||||
// test all possible six-line files with missing newlines.
|
||||
let _ = std::fs::create_dir("target");
|
||||
for &a in &[0, 1, 2] {
|
||||
for &b in &[0, 1, 2] {
|
||||
for &c in &[0, 1, 2] {
|
||||
for &d in &[0, 1, 2] {
|
||||
for &e in &[0, 1, 2] {
|
||||
for &f in &[0, 1, 2] {
|
||||
use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
use std::process::Command;
|
||||
let mut alef = Vec::new();
|
||||
let mut bet = Vec::new();
|
||||
alef.write_all(if a == 0 { b"\n" } else { b"b\n" }).unwrap();
|
||||
if a != 2 {
|
||||
bet.write_all(b"b\n").unwrap();
|
||||
}
|
||||
alef.write_all(if b == 0 { b"\n" } else { b"d\n" }).unwrap();
|
||||
if b != 2 {
|
||||
bet.write_all(b"d\n").unwrap();
|
||||
}
|
||||
alef.write_all(if c == 0 { b"\n" } else { b"f\n" }).unwrap();
|
||||
if c != 2 {
|
||||
bet.write_all(b"f\n").unwrap();
|
||||
}
|
||||
alef.write_all(if d == 0 { b"\n" } else { b"h\n" }).unwrap();
|
||||
if d != 2 {
|
||||
bet.write_all(b"h\n").unwrap();
|
||||
}
|
||||
alef.write_all(if e == 0 { b"\n" } else { b"j\n" }).unwrap();
|
||||
if e != 2 {
|
||||
bet.write_all(b"j\n").unwrap();
|
||||
}
|
||||
alef.write_all(if f == 0 { b"\n" } else { b"l\n" }).unwrap();
|
||||
if f != 2 {
|
||||
bet.write_all(b"l\n").unwrap();
|
||||
}
|
||||
// This test diff is intentionally reversed.
|
||||
// We want it to turn the alef into bet.
|
||||
let diff = diff(&alef, &bet);
|
||||
File::create("target/ab_.diff")
|
||||
.unwrap()
|
||||
.write_all(&diff)
|
||||
.unwrap();
|
||||
let mut fa = File::create("target/alef_").unwrap();
|
||||
fa.write_all(&alef[..]).unwrap();
|
||||
let mut fb = File::create("target/bet_").unwrap();
|
||||
fb.write_all(&bet[..]).unwrap();
|
||||
let _ = fa;
|
||||
let _ = fb;
|
||||
let output = Command::new("patch")
|
||||
.arg("-p0")
|
||||
.arg("target/alef_")
|
||||
.stdin(File::open("target/ab_.diff").unwrap())
|
||||
.output()
|
||||
.unwrap();
|
||||
if !output.status.success() {
|
||||
panic!("{:?}", output);
|
||||
}
|
||||
//println!("{}", String::from_utf8_lossy(&output.stdout));
|
||||
//println!("{}", String::from_utf8_lossy(&output.stderr));
|
||||
let alef = fs::read("target/alef_").unwrap();
|
||||
assert_eq!(alef, bet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_permutations_reverse() {
|
||||
// test all possible six-line files.
|
||||
let _ = std::fs::create_dir("target");
|
||||
for &a in &[0, 1, 2] {
|
||||
for &b in &[0, 1, 2] {
|
||||
for &c in &[0, 1, 2] {
|
||||
for &d in &[0, 1, 2] {
|
||||
for &e in &[0, 1, 2] {
|
||||
for &f in &[0, 1, 2] {
|
||||
use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
use std::process::Command;
|
||||
let mut alef = Vec::new();
|
||||
let mut bet = Vec::new();
|
||||
alef.write_all(if a == 0 { b"a\n" } else { b"f\n" })
|
||||
.unwrap();
|
||||
if a != 2 {
|
||||
bet.write_all(b"a\n").unwrap();
|
||||
}
|
||||
alef.write_all(if b == 0 { b"b\n" } else { b"e\n" })
|
||||
.unwrap();
|
||||
if b != 2 {
|
||||
bet.write_all(b"b\n").unwrap();
|
||||
}
|
||||
alef.write_all(if c == 0 { b"c\n" } else { b"d\n" })
|
||||
.unwrap();
|
||||
if c != 2 {
|
||||
bet.write_all(b"c\n").unwrap();
|
||||
}
|
||||
alef.write_all(if d == 0 { b"d\n" } else { b"c\n" })
|
||||
.unwrap();
|
||||
if d != 2 {
|
||||
bet.write_all(b"d\n").unwrap();
|
||||
}
|
||||
alef.write_all(if e == 0 { b"e\n" } else { b"b\n" })
|
||||
.unwrap();
|
||||
if e != 2 {
|
||||
bet.write_all(b"e\n").unwrap();
|
||||
}
|
||||
alef.write_all(if f == 0 { b"f\n" } else { b"a\n" })
|
||||
.unwrap();
|
||||
if f != 2 {
|
||||
bet.write_all(b"f\n").unwrap();
|
||||
}
|
||||
// This test diff is intentionally reversed.
|
||||
// We want it to turn the alef into bet.
|
||||
let diff = diff(&alef, &bet);
|
||||
File::create("target/abr.diff")
|
||||
.unwrap()
|
||||
.write_all(&diff)
|
||||
.unwrap();
|
||||
let mut fa = File::create("target/alefr").unwrap();
|
||||
fa.write_all(&alef[..]).unwrap();
|
||||
let mut fb = File::create("target/betr").unwrap();
|
||||
fb.write_all(&bet[..]).unwrap();
|
||||
let _ = fa;
|
||||
let _ = fb;
|
||||
let output = Command::new("patch")
|
||||
.arg("-p0")
|
||||
.arg("target/alefr")
|
||||
.stdin(File::open("target/abr.diff").unwrap())
|
||||
.output()
|
||||
.unwrap();
|
||||
if !output.status.success() {
|
||||
panic!("{:?}", output);
|
||||
}
|
||||
//println!("{}", String::from_utf8_lossy(&output.stdout));
|
||||
//println!("{}", String::from_utf8_lossy(&output.stderr));
|
||||
let alef = fs::read("target/alefr").unwrap();
|
||||
assert_eq!(alef, bet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "unified-diff"
|
||||
version = "0.3.0"
|
||||
authors = [
|
||||
"Michael Howell <michael@notriddle.com>",
|
||||
"The Rust Project Developers"
|
||||
]
|
||||
edition = "2018"
|
||||
description = "An implementation of the GNU unified diff format"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/notriddle/diffutils"
|
||||
exclude = [ "fuzz" ]
|
||||
|
||||
[dependencies]
|
||||
diff = "0.1.10"
|
||||
@@ -380,6 +380,7 @@ pub fn diff(
|
||||
#[test]
|
||||
fn test_permutations() {
|
||||
// test all possible six-line files.
|
||||
let _ = std::fs::create_dir("target");
|
||||
for &a in &[0, 1, 2] {
|
||||
for &b in &[0, 1, 2] {
|
||||
for &c in &[0, 1, 2] {
|
||||
@@ -457,6 +458,7 @@ fn test_permutations() {
|
||||
#[test]
|
||||
fn test_permutations_missing_line_ending() {
|
||||
// test all possible six-line files with missing newlines.
|
||||
let _ = std::fs::create_dir("target");
|
||||
for &a in &[0, 1, 2] {
|
||||
for &b in &[0, 1, 2] {
|
||||
for &c in &[0, 1, 2] {
|
||||
@@ -549,6 +551,7 @@ fn test_permutations_missing_line_ending() {
|
||||
#[test]
|
||||
fn test_permutations_empty_lines() {
|
||||
// test all possible six-line files with missing newlines.
|
||||
let _ = std::fs::create_dir("target");
|
||||
for &a in &[0, 1, 2] {
|
||||
for &b in &[0, 1, 2] {
|
||||
for &c in &[0, 1, 2] {
|
||||
@@ -636,6 +639,7 @@ fn test_permutations_empty_lines() {
|
||||
#[test]
|
||||
fn test_permutations_missing_lines() {
|
||||
// test all possible six-line files.
|
||||
let _ = std::fs::create_dir("target");
|
||||
for &a in &[0, 1, 2] {
|
||||
for &b in &[0, 1, 2] {
|
||||
for &c in &[0, 1, 2] {
|
||||
@@ -707,6 +711,7 @@ fn test_permutations_missing_lines() {
|
||||
#[test]
|
||||
fn test_permutations_reverse() {
|
||||
// test all possible six-line files.
|
||||
let _ = std::fs::create_dir("target");
|
||||
for &a in &[0, 1, 2] {
|
||||
for &b in &[0, 1, 2] {
|
||||
for &c in &[0, 1, 2] {
|
||||
Reference in New Issue
Block a user