From e72ea046b7984646e7df309ad89100534ade7712 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 9 Mar 2021 16:23:46 -0700 Subject: [PATCH] Add context and normal diff modes --- .gitignore | 3 + Cargo.toml | 20 +- README.md | 61 +- bin/diff/Cargo.toml | 12 + bin/diff/src/main.rs | 50 ++ bin/diff/src/params.rs | 232 ++++++ lib/context-diff/Cargo.toml | 15 + lib/context-diff/src/lib.rs | 772 ++++++++++++++++++ lib/normal-diff/Cargo.toml | 15 + lib/normal-diff/src/lib.rs | 362 ++++++++ lib/unified-diff/Cargo.toml | 15 + {fuzz => lib/unified-diff/fuzz}/.gitignore | 0 {fuzz => lib/unified-diff/fuzz}/Cargo.toml | 0 .../fuzz}/fuzz_targets/fuzz_patch.rs | 0 {src => lib/unified-diff/src}/lib.rs | 5 + 15 files changed, 1526 insertions(+), 36 deletions(-) create mode 100644 bin/diff/Cargo.toml create mode 100644 bin/diff/src/main.rs create mode 100644 bin/diff/src/params.rs create mode 100644 lib/context-diff/Cargo.toml create mode 100644 lib/context-diff/src/lib.rs create mode 100644 lib/normal-diff/Cargo.toml create mode 100644 lib/normal-diff/src/lib.rs create mode 100644 lib/unified-diff/Cargo.toml rename {fuzz => lib/unified-diff/fuzz}/.gitignore (100%) rename {fuzz => lib/unified-diff/fuzz}/Cargo.toml (100%) rename {fuzz => lib/unified-diff/fuzz}/fuzz_targets/fuzz_patch.rs (100%) rename {src => lib/unified-diff/src}/lib.rs (99%) diff --git a/.gitignore b/.gitignore index 46f3847..e90ad7d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ /target +/lib/normal-diff/target +/lib/context-diff/target +/lib/unified-diff/target Cargo.lock *.swp diff --git a/Cargo.toml b/Cargo.toml index 5b2d07f..62bfa42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,15 +1,7 @@ -[package] -name = "unified-diff" -version = "0.2.1" -authors = [ - "Michael Howell ", - "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" diff --git a/README.md b/README.md index 103c010..a964abc 100644 --- a/README.md +++ b/README.md @@ -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 ", -+ "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 ``` diff --git a/bin/diff/Cargo.toml b/bin/diff/Cargo.toml new file mode 100644 index 0000000..7f3cd7c --- /dev/null +++ b/bin/diff/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "diff" +version = "0.1.0" +authors = ["Michael Howell "] +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" } diff --git a/bin/diff/src/main.rs b/bin/diff/src/main.rs new file mode 100644 index 0000000..cc36661 --- /dev/null +++ b/bin/diff/src/main.rs @@ -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 = 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(()) +} diff --git a/bin/diff/src/params.rs b/bin/diff/src/params.rs new file mode 100644 index 0000000..f44aeeb --- /dev/null +++ b/bin/diff/src/params.rs @@ -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 { + 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>(opts: I) -> Result { + let mut opts = opts.into_iter(); + // parse CLI + let exe = match opts.next() { + Some(from) => from, + None => { + return Err(format!("Usage: ")); + } + }; + 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: {} ", 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::>()).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: {} ", 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: {} ", 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: {} ", 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()); + } +} diff --git a/lib/context-diff/Cargo.toml b/lib/context-diff/Cargo.toml new file mode 100644 index 0000000..5bf1dd4 --- /dev/null +++ b/lib/context-diff/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "context-diff" +version = "0.1.0" +authors = [ + "Michael Howell ", + "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" diff --git a/lib/context-diff/src/lib.rs b/lib/context-diff/src/lib.rs new file mode 100644 index 0000000..08fe221 --- /dev/null +++ b/lib/context-diff/src/lib.rs @@ -0,0 +1,772 @@ +use std::collections::VecDeque; +use std::io::Write; +use std::mem; + +#[derive(Debug, PartialEq)] +pub enum DiffLine { + Context(Vec), + Change(Vec), + Add(Vec), +} + +#[derive(Debug, PartialEq)] +struct Mismatch { + pub line_number_expected: usize, + pub line_number_actual: usize, + pub expected: Vec, + pub actual: Vec, + pub expected_missing_nl: bool, + pub actual_missing_nl: bool, + pub expected_all_context: bool, + pub actual_all_context: bool, +} + +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(), + expected_missing_nl: false, + actual_missing_nl: false, + expected_all_context: false, + actual_all_context: false, + } + } +} + +// Produces a diff between the expected output and actual output. +fn make_diff(expected: &[u8], actual: &[u8], context_size: usize) -> Vec { + let mut line_number_expected = 1; + let mut line_number_actual = 1; + let mut context_queue: VecDeque<&[u8]> = VecDeque::with_capacity(context_size); + let mut lines_since_mismatch = context_size + 1; + let mut results = Vec::new(); + let mut mismatch = Mismatch::new(0, 0); + + 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(); + } + + // Rust only allows allocations to grow to isize::MAX, and this is bigger than that. + let mut expected_lines_change_idx: usize = !0; + + for result in diff::slice(&expected_lines, &actual_lines) { + match result { + diff::Result::Left(str) => { + if lines_since_mismatch > context_size && lines_since_mismatch > 0 { + results.push(mismatch); + mismatch = Mismatch::new( + line_number_expected - context_queue.len(), + line_number_actual - context_queue.len(), + ); + } + + while let Some(line) = context_queue.pop_front() { + mismatch.expected.push(DiffLine::Context(line.to_vec())); + mismatch.actual.push(DiffLine::Context(line.to_vec())); + } + + expected_lines_change_idx = mismatch.expected.len(); + mismatch.expected.push(DiffLine::Add(str.to_vec())); + if line_number_expected > expected_lines_count { + mismatch.expected_missing_nl = true; + } + line_number_expected += 1; + lines_since_mismatch = 0; + } + diff::Result::Right(str) => { + if lines_since_mismatch > context_size && lines_since_mismatch > 0 { + results.push(mismatch); + mismatch = Mismatch::new( + line_number_expected - context_queue.len(), + line_number_actual - context_queue.len(), + ); + expected_lines_change_idx = !0; + } + + while let Some(line) = context_queue.pop_front() { + mismatch.expected.push(DiffLine::Context(line.to_vec())); + mismatch.actual.push(DiffLine::Context(line.to_vec())); + } + + if let Some(DiffLine::Add(content)) = + mismatch.expected.get_mut(expected_lines_change_idx) + { + let content = mem::replace(content, Vec::new()); + mismatch.expected[expected_lines_change_idx] = DiffLine::Change(content); + expected_lines_change_idx = expected_lines_change_idx.wrapping_sub(1); // if 0, becomes !0 + mismatch.actual.push(DiffLine::Change(str.to_vec())); + } else { + mismatch.actual.push(DiffLine::Add(str.to_vec())); + } + if line_number_actual > actual_lines_count { + mismatch.actual_missing_nl = true; + } + line_number_actual += 1; + lines_since_mismatch = 0; + } + diff::Result::Both(str, _) => { + expected_lines_change_idx = !0; + // if one of them is missing a newline and the other isn't, then they don't actually match + if (line_number_actual > actual_lines_count) + && (line_number_expected > expected_lines_count) + { + if context_queue.len() < context_size { + while let Some(line) = context_queue.pop_front() { + mismatch.expected.push(DiffLine::Context(line.to_vec())); + mismatch.actual.push(DiffLine::Context(line.to_vec())); + } + if lines_since_mismatch < context_size { + mismatch.expected.push(DiffLine::Context(str.to_vec())); + mismatch.actual.push(DiffLine::Context(str.to_vec())); + mismatch.expected_missing_nl = true; + mismatch.actual_missing_nl = true; + } + } + lines_since_mismatch = 0; + } else if line_number_actual > actual_lines_count { + if lines_since_mismatch >= context_size && lines_since_mismatch > 0 { + results.push(mismatch); + mismatch = Mismatch::new( + line_number_expected - context_queue.len(), + line_number_actual - context_queue.len(), + ); + } + while let Some(line) = context_queue.pop_front() { + mismatch.expected.push(DiffLine::Context(line.to_vec())); + mismatch.actual.push(DiffLine::Context(line.to_vec())); + } + mismatch.expected.push(DiffLine::Change(str.to_vec())); + mismatch.actual.push(DiffLine::Change(str.to_vec())); + mismatch.actual_missing_nl = true; + lines_since_mismatch = 0; + } else if line_number_expected > expected_lines_count { + if lines_since_mismatch >= context_size && lines_since_mismatch > 0 { + results.push(mismatch); + mismatch = Mismatch::new( + line_number_expected - context_queue.len(), + line_number_actual - context_queue.len(), + ); + } + while let Some(line) = context_queue.pop_front() { + mismatch.expected.push(DiffLine::Context(line.to_vec())); + mismatch.actual.push(DiffLine::Context(line.to_vec())); + } + mismatch.expected.push(DiffLine::Change(str.to_vec())); + mismatch.expected_missing_nl = true; + mismatch.actual.push(DiffLine::Change(str.to_vec())); + lines_since_mismatch = 0; + } else { + debug_assert!(context_queue.len() <= context_size); + if context_queue.len() >= context_size { + let _ = context_queue.pop_front(); + } + if lines_since_mismatch < context_size { + mismatch.expected.push(DiffLine::Context(str.to_vec())); + mismatch.actual.push(DiffLine::Context(str.to_vec())); + } else if context_size > 0 { + context_queue.push_back(str); + } + lines_since_mismatch += 1; + } + line_number_expected += 1; + line_number_actual += 1; + } + } + } + + results.push(mismatch); + results.remove(0); + + if results.len() == 0 && expected_lines_count != actual_lines_count { + let mut mismatch = Mismatch::new(expected_lines.len(), actual_lines.len()); + // empty diff and only expected lines has a missing line at end + if expected_lines_count != expected_lines.len() { + mismatch.expected.push(DiffLine::Change( + expected_lines + .pop() + .expect("can't be empty; produced by split()") + .to_vec(), + )); + mismatch.expected_missing_nl = true; + mismatch.actual.push(DiffLine::Change( + actual_lines + .pop() + .expect("can't be empty; produced by split()") + .to_vec(), + )); + results.push(mismatch); + } else if actual_lines_count != actual_lines.len() { + mismatch.expected.push(DiffLine::Change( + expected_lines + .pop() + .expect("can't be empty; produced by split()") + .to_vec(), + )); + mismatch.actual.push(DiffLine::Change( + actual_lines + .pop() + .expect("can't be empty; produced by split()") + .to_vec(), + )); + mismatch.actual_missing_nl = true; + results.push(mismatch); + } + } + + // hunks with pure context lines get truncated to empty + for mismatch in &mut results { + if mismatch + .expected + .iter() + .find(|x| !matches!(x, DiffLine::Context(_))) + .is_none() + { + mismatch.expected_all_context = true; + } + if mismatch + .actual + .iter() + .find(|x| !matches!(x, DiffLine::Context(_))) + .is_none() + { + mismatch.actual_all_context = true; + } + } + + results +} + +pub fn diff( + expected: &[u8], + expected_filename: &str, + actual: &[u8], + actual_filename: &str, + context_size: usize, +) -> Vec { + let mut output = + format!("*** {}\t\n--- {}\t\n", expected_filename, actual_filename).into_bytes(); + let diff_results = make_diff(expected, actual, context_size); + if diff_results.len() == 0 { + return Vec::new(); + }; + for result in diff_results { + let mut line_number_expected = result.line_number_expected; + let mut line_number_actual = result.line_number_actual; + let mut expected_count = result.expected.len(); + let mut actual_count = result.actual.len(); + if expected_count == 0 { + line_number_expected -= 1; + expected_count = 1; + } + if actual_count == 0 { + line_number_actual -= 1; + actual_count = 1; + } + let end_line_number_expected = expected_count + line_number_expected - 1; + let end_line_number_actual = actual_count + line_number_actual - 1; + let exp_start = if end_line_number_expected == line_number_expected { + String::new() + } else { + format!("{},", line_number_expected) + }; + let act_start = if end_line_number_actual == line_number_actual { + String::new() + } else { + format!("{},", line_number_actual) + }; + writeln!( + output, + "***************\n*** {}{} ****", + exp_start, end_line_number_expected + ) + .expect("write to Vec is infallible"); + if !result.expected_all_context { + for line in result.expected { + match line { + DiffLine::Context(e) => { + write!(output, " ").expect("write to Vec is infallible"); + output.write_all(&e).expect("write to Vec is infallible"); + writeln!(output).unwrap(); + } + DiffLine::Change(e) => { + write!(output, "! ").expect("write to Vec is infallible"); + output.write_all(&e).expect("write to Vec is infallible"); + writeln!(output).unwrap(); + } + DiffLine::Add(e) => { + write!(output, "- ").expect("write to Vec is infallible"); + output.write_all(&e).expect("write to Vec is infallible"); + writeln!(output).unwrap(); + } + } + } + if result.expected_missing_nl { + writeln!(output, r"\ No newline at end of file") + .expect("write to Vec is infallible"); + } + } + writeln!(output, "--- {}{} ----", act_start, end_line_number_actual) + .expect("write to Vec is infallible"); + if !result.actual_all_context { + for line in result.actual { + match line { + DiffLine::Context(e) => { + write!(output, " ").expect("write to Vec is infallible"); + output.write_all(&e).expect("write to Vec is infallible"); + writeln!(output).unwrap(); + } + DiffLine::Change(e) => { + write!(output, "! ").expect("write to Vec is infallible"); + output.write_all(&e).expect("write to Vec is infallible"); + writeln!(output).unwrap(); + } + DiffLine::Add(e) => { + write!(output, "+ ").expect("write to Vec is infallible"); + output.write_all(&e).expect("write to Vec is infallible"); + writeln!(output).unwrap(); + } + } + } + if result.actual_missing_nl { + writeln!(output, r"\ No newline at end of file") + .expect("write to Vec is infallible"); + } + } + } + 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, "a/alef", &bet, "target/alef", 2); + 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("--context") + .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_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] { + for &d in &[0, 1, 2] { + for &e in &[0, 1, 2] { + for &f in &[0, 1, 2] { + for &g 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(); + } + match g { + 0 => { + alef.pop(); + } + 1 => { + bet.pop(); + } + 2 => { + alef.pop(); + bet.pop(); + } + _ => unreachable!(), + } + if alef.is_empty() && bet.is_empty() { + continue; + }; + // This test diff is intentionally reversed. + // We want it to turn the alef into bet. + let diff = diff(&alef, "a/alefn", &bet, "target/alefn", 2); + File::create("target/abn.diff") + .unwrap() + .write_all(&diff) + .unwrap(); + let mut fa = File::create("target/alefn").unwrap(); + fa.write_all(&alef[..]).unwrap(); + let mut fb = File::create("target/betn").unwrap(); + fb.write_all(&bet[..]).unwrap(); + let _ = fa; + let _ = fb; + let output = Command::new("patch") + .arg("-p0") + .arg("--context") + .stdin(File::open("target/abn.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/alefn").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] { + for &g in &[0, 1, 2, 3] { + 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(); + } + match g { + 0 => { + alef.pop(); + } + 1 => { + bet.pop(); + } + 2 => { + alef.pop(); + bet.pop(); + } + 3 => {} + _ => unreachable!(), + } + // This test diff is intentionally reversed. + // We want it to turn the alef into bet. + let diff = diff(&alef, "a/alef_", &bet, "target/alef_", 2); + 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("--context") + .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_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] { + 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"" }).unwrap(); + if a != 2 { + bet.write_all(b"b\n").unwrap(); + } + alef.write_all(if b == 0 { b"c\n" } else { b"" }).unwrap(); + if b != 2 { + bet.write_all(b"d\n").unwrap(); + } + alef.write_all(if c == 0 { b"e\n" } else { b"" }).unwrap(); + if c != 2 { + bet.write_all(b"f\n").unwrap(); + } + alef.write_all(if d == 0 { b"g\n" } else { b"" }).unwrap(); + if d != 2 { + bet.write_all(b"h\n").unwrap(); + } + alef.write_all(if e == 0 { b"i\n" } else { b"" }).unwrap(); + if e != 2 { + bet.write_all(b"j\n").unwrap(); + } + alef.write_all(if f == 0 { b"k\n" } else { b"" }).unwrap(); + if f != 2 { + bet.write_all(b"l\n").unwrap(); + } + if alef.is_empty() && bet.is_empty() { + continue; + }; + // This test diff is intentionally reversed. + // We want it to turn the alef into bet. + let diff = diff(&alef, "a/alefx", &bet, "target/alefx", 2); + File::create("target/abx.diff") + .unwrap() + .write_all(&diff) + .unwrap(); + let mut fa = File::create("target/alefx").unwrap(); + fa.write_all(&alef[..]).unwrap(); + let mut fb = File::create("target/betx").unwrap(); + fb.write_all(&bet[..]).unwrap(); + let _ = fa; + let _ = fb; + let output = Command::new("patch") + .arg("-p0") + .arg("--context") + .stdin(File::open("target/abx.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/alefx").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, "a/alefr", &bet, "target/alefr", 2); + 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("--context") + .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); + } + } + } + } + } + } +} diff --git a/lib/normal-diff/Cargo.toml b/lib/normal-diff/Cargo.toml new file mode 100644 index 0000000..0bde96c --- /dev/null +++ b/lib/normal-diff/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "normal-diff" +version = "0.1.0" +authors = [ + "Michael Howell ", + "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" diff --git a/lib/normal-diff/src/lib.rs b/lib/normal-diff/src/lib.rs new file mode 100644 index 0000000..d102faf --- /dev/null +++ b/lib/normal-diff/src/lib.rs @@ -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>, + pub actual: Vec>, +} + +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 { + 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 { + 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); + } + } + } + } + } + } +} diff --git a/lib/unified-diff/Cargo.toml b/lib/unified-diff/Cargo.toml new file mode 100644 index 0000000..42df1e2 --- /dev/null +++ b/lib/unified-diff/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "unified-diff" +version = "0.3.0" +authors = [ + "Michael Howell ", + "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" diff --git a/fuzz/.gitignore b/lib/unified-diff/fuzz/.gitignore similarity index 100% rename from fuzz/.gitignore rename to lib/unified-diff/fuzz/.gitignore diff --git a/fuzz/Cargo.toml b/lib/unified-diff/fuzz/Cargo.toml similarity index 100% rename from fuzz/Cargo.toml rename to lib/unified-diff/fuzz/Cargo.toml diff --git a/fuzz/fuzz_targets/fuzz_patch.rs b/lib/unified-diff/fuzz/fuzz_targets/fuzz_patch.rs similarity index 100% rename from fuzz/fuzz_targets/fuzz_patch.rs rename to lib/unified-diff/fuzz/fuzz_targets/fuzz_patch.rs diff --git a/src/lib.rs b/lib/unified-diff/src/lib.rs similarity index 99% rename from src/lib.rs rename to lib/unified-diff/src/lib.rs index 2b756a4..a9f29a1 100644 --- a/src/lib.rs +++ b/lib/unified-diff/src/lib.rs @@ -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] {