diff --git a/src/diff/mod.rs b/src/diff/mod.rs index a456c41..eb18e4b 100644 --- a/src/diff/mod.rs +++ b/src/diff/mod.rs @@ -3,7 +3,7 @@ use crate::{ range::{DiffRange, SliceLike}, utils::Classifier, }; -use std::{cmp, ops}; +use std::{borrow::Cow, cmp, ops}; mod cleanup; mod myers; @@ -46,6 +46,8 @@ where pub struct DiffOptions { compact: bool, context_len: usize, + original_filename: Option>, + modified_filename: Option>, } impl DiffOptions { @@ -57,6 +59,8 @@ impl DiffOptions { Self { compact: true, context_len: 3, + original_filename: Some("original".into()), + modified_filename: Some("modified".into()), } } @@ -76,6 +80,28 @@ impl DiffOptions { self } + /// Set the filename to be used in the patch for the original text + /// + /// If not set, the default value is "original". + pub fn set_original_filename(&mut self, filename: T) -> &mut Self + where + T: Into>, + { + self.original_filename = Some(filename.into()); + self + } + + /// Set the filename to be used in the patch for the modified text + /// + /// If not set, the default value is "modified". + pub fn set_modified_filename(&mut self, filename: T) -> &mut Self + where + T: Into>, + { + self.modified_filename = Some(filename.into()); + 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> { @@ -102,7 +128,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( + self.original_filename.clone(), + self.modified_filename.clone(), + hunks, + ) } /// Create a patch between two potentially non-utf8 texts @@ -118,7 +148,20 @@ 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) + + // helper function to convert a utf8 cow to a bytes cow + fn cow_str_to_bytes(cow: Cow<'static, str>) -> Cow<'static, [u8]> { + match cow { + Cow::Borrowed(b) => Cow::Borrowed(b.as_bytes()), + Cow::Owned(o) => Cow::Owned(o.into_bytes()), + } + } + + Patch::new( + self.original_filename.clone().map(cow_str_to_bytes), + self.modified_filename.clone().map(cow_str_to_bytes), + hunks, + ) } pub(crate) fn diff_slice<'a, T: PartialEq>( @@ -358,3 +401,42 @@ fn build_edit_script(solution: &[DiffRange<[T]>]) -> Vec { edit_script } + +#[cfg(test)] +mod test { + use super::DiffOptions; + + #[test] + fn set_original_and_modified_filenames() { + let original = "\ +I am afraid, however, that all I have known - that my story - will be forgotten. +I am afraid for the world that is to come. +Afraid that my plans will fail. +Afraid of a doom worse than the Deepness. +"; + let modified = "\ +I am afraid, however, that all I have known - that my story - will be forgotten. +I am afraid for the world that is to come. +Afraid that Alendi will fail. +Afraid of a doom brought by the Deepness. +"; + let expected = "\ +--- the old version ++++ the better version +@@ -1,4 +1,4 @@ + I am afraid, however, that all I have known - that my story - will be forgotten. + I am afraid for the world that is to come. +-Afraid that my plans will fail. +-Afraid of a doom worse than the Deepness. ++Afraid that Alendi will fail. ++Afraid of a doom brought by the Deepness. +"; + + let patch = DiffOptions::new() + .set_original_filename("the old version") + .set_modified_filename("the better version") + .create_patch(original, modified); + + assert_eq!(patch.to_string(), expected); + } +} diff --git a/src/patch/mod.rs b/src/patch/mod.rs index 2b98246..8b166bf 100644 --- a/src/patch/mod.rs +++ b/src/patch/mod.rs @@ -144,6 +144,7 @@ where struct Filename<'a, T: ToOwned + ?Sized>(Cow<'a, T>); const ESCAPED_CHARS: &[char] = &['\n', '\t', '\0', '\r', '\"', '\\']; +#[allow(clippy::byte_char_slices)] const ESCAPED_CHARS_BYTES: &[u8] = &[b'\n', b'\t', b'\0', b'\r', b'\"', b'\\']; impl Filename<'_, str> {