diff --git a/src/diff/mod.rs b/src/diff/mod.rs index a456c41..de81664 100644 --- a/src/diff/mod.rs +++ b/src/diff/mod.rs @@ -43,12 +43,14 @@ where /// A collection of options for modifying the way a diff is performed #[derive(Debug)] -pub struct DiffOptions { +pub struct DiffOptions<'a> { compact: bool, context_len: usize, + original: &'a str, + modified: &'a str, } -impl DiffOptions { +impl<'a> DiffOptions<'a> { /// Construct a new `DiffOptions` with default settings /// /// ## Defaults @@ -57,6 +59,8 @@ impl DiffOptions { Self { compact: true, context_len: 3, + original: "original", + modified: "modified", } } @@ -76,9 +80,21 @@ impl DiffOptions { self } + /// Set the name of the old file should be used when producing a patch + pub fn set_original(&mut self, original: &'a str) -> &mut Self { + self.original = original; + self + } + + /// Set the name of the new file that should be used when producing a patch + pub fn set_modified(&mut self, modified: &'a str) -> &mut Self { + self.modified = modified; + self + } + // TODO determine if this should be exposed in the public API #[allow(dead_code)] - fn diff<'a>(&self, original: &'a str, modified: &'a str) -> Vec> { + fn diff(&self, original: &'a str, modified: &'a str) -> Vec> { let solution = myers::diff(original.as_bytes(), modified.as_bytes()); let mut solution = solution @@ -94,7 +110,7 @@ impl DiffOptions { } /// Produce a Patch between two texts based on the configured options - pub fn create_patch<'a>(&self, original: &'a str, modified: &'a str) -> Patch<'a, str> { + pub fn create_patch(&self, original: &'a str, modified: &'a str) -> Patch<'a, str> { let mut classifier = Classifier::default(); let (old_lines, old_ids) = classifier.classify_lines(original); let (new_lines, new_ids) = classifier.classify_lines(modified); @@ -102,11 +118,11 @@ impl DiffOptions { let solution = self.diff_slice(&old_ids, &new_ids); let hunks = to_hunks(&old_lines, &new_lines, &solution, self.context_len); - Patch::new(Some("original"), Some("modified"), hunks) + Patch::new(Some(self.original), Some(self.modified), hunks) } /// Create a patch between two potentially non-utf8 texts - pub fn create_patch_bytes<'a>( + pub fn create_patch_bytes( &self, original: &'a [u8], modified: &'a [u8], @@ -118,10 +134,10 @@ impl DiffOptions { let solution = self.diff_slice(&old_ids, &new_ids); let hunks = to_hunks(&old_lines, &new_lines, &solution, self.context_len); - Patch::new(Some(&b"original"[..]), Some(&b"modified"[..]), hunks) + Patch::new(Some(self.original.as_bytes()), Some(self.modified.as_bytes()), hunks) } - pub(crate) fn diff_slice<'a, T: PartialEq>( + pub(crate) fn diff_slice( &self, old: &'a [T], new: &'a [T], @@ -136,7 +152,7 @@ impl DiffOptions { } } -impl Default for DiffOptions { +impl<'a> Default for DiffOptions<'a> { fn default() -> Self { Self::new() } diff --git a/src/diff/tests.rs b/src/diff/tests.rs index aeb1558..7ee9d7b 100644 --- a/src/diff/tests.rs +++ b/src/diff/tests.rs @@ -3,6 +3,7 @@ use crate::{ apply::apply, diff::{Diff, DiffRange}, patch::Patch, + PatchFormatter, range::Range, }; @@ -463,6 +464,27 @@ The door of all subtleties! "; opts.set_context_len(1); assert_patch!(opts, lao, tzu, expected); + + let expected = "\ +--- from ++++ to +@@ -1,5 +1,4 @@ +-The Way that can be told of is not the eternal Way; +-The name that can be named is not the eternal name. + The Nameless is the origin of Heaven and Earth; +-The Named is the mother of all things. ++The named is the mother of all things. ++ + Therefore let there always be non-being, +@@ -11 +10,4 @@ + they have different names. ++They both may be called deep and profound. ++Deeper and more profound, ++The door of all subtleties! +"; + opts.set_original("from"); + opts.set_modified("to"); + assert_patch!(opts, lao, tzu, expected); } #[test] @@ -518,6 +540,35 @@ fn no_newline_at_eof() { assert_patch!(old, new, expected); } +#[test] +fn without_no_newline_at_eof() { + let old = "old line"; + let new = "new line"; + let expected = "\ +--- original ++++ modified +@@ -1 +1 @@ +-old line ++new line +"; + + let f = PatchFormatter::new().without_missing_newline_message(); + let patch = create_patch(old, new); + let bpatch = create_patch_bytes(old.as_bytes(), new.as_bytes()); + let patch_str = format!("{}", f.fmt_patch(&patch)); + let mut patch_bytes = Vec::new(); + f.write_patch_into(&bpatch, &mut patch_bytes).unwrap(); + + assert_eq!(patch_str, expected); + assert_eq!(patch_bytes, patch_str.as_bytes()); + assert_eq!(patch_bytes, expected.as_bytes()); + assert_eq!(apply(old, &patch).unwrap(), new); + assert_eq!( + crate::apply_bytes(old.as_bytes(), &bpatch).unwrap(), + new.as_bytes() + ); +} + #[test] fn myers_diffy_vs_git() { let original = "\ diff --git a/src/patch/format.rs b/src/patch/format.rs index 0b8276a..9e7ec41 100644 --- a/src/patch/format.rs +++ b/src/patch/format.rs @@ -9,6 +9,7 @@ use std::{ #[derive(Debug)] pub struct PatchFormatter { with_color: bool, + without_missing_newline_message: bool, context: Style, delete: Style, @@ -23,6 +24,7 @@ impl PatchFormatter { pub fn new() -> Self { Self { with_color: false, + without_missing_newline_message: false, context: Style::new(), delete: Color::Red.normal(), @@ -39,6 +41,12 @@ impl PatchFormatter { self } + /// Enable formatting a patch without a "No newline at end of file" message + pub fn without_missing_newline_message(mut self) -> Self { + self.without_missing_newline_message = true; + self + } + /// Returns a `Display` impl which can be used to print a Patch pub fn fmt_patch<'a>(&'a self, patch: &'a Patch<'a, str>) -> impl Display + 'a { PatchDisplay { f: self, patch } @@ -238,7 +246,9 @@ impl + ?Sized> LineDisplay<'_, T> { if !line.ends_with(b"\n") { writeln!(w)?; - writeln!(w, "{}", NO_NEWLINE_AT_EOF)?; + if !self.f.without_missing_newline_message { + writeln!(w, "{}", NO_NEWLINE_AT_EOF)?; + } } Ok(()) @@ -269,7 +279,9 @@ impl Display for LineDisplay<'_, str> { if !line.ends_with('\n') { writeln!(f)?; - writeln!(f, "{}", NO_NEWLINE_AT_EOF)?; + if !self.f.without_missing_newline_message { + writeln!(f, "{}", NO_NEWLINE_AT_EOF)?; + } } Ok(())