From 160c4ec2638c0b36b9e8f37d74ca3279429bca81 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Tue, 28 Jan 2025 12:57:37 -0800 Subject: [PATCH 1/2] feat: control filenames in DiffOptions --- src/diff/mod.rs | 75 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/src/diff/mod.rs b/src/diff/mod.rs index a456c41..f3c2d8b 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: None, + modified_filename: None, } } @@ -76,6 +80,22 @@ 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: impl ToString) -> &mut Self { + self.original_filename = Some(filename.to_string()); + 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: impl ToString) -> &mut Self { + self.modified_filename = Some(filename.to_string()); + 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 +122,19 @@ 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 + .as_ref() + .map_or(Some(Cow::Borrowed("original")), |s| { + Some(Cow::Owned(s.clone())) + }), + self.modified_filename + .as_ref() + .map_or(Some(Cow::Borrowed("modified")), |s| { + Some(Cow::Owned(s.clone())) + }), + hunks, + ) } /// Create a patch between two potentially non-utf8 texts @@ -358,3 +390,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); + } +} From 9903e7fe7ca516ced033c1b4ee68da21021739f3 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 29 Jan 2025 12:16:54 -0600 Subject: [PATCH 2/2] tweaks --- src/diff/mod.rs | 49 +++++++++++++++++++++++++++++------------------- src/patch/mod.rs | 1 + 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/diff/mod.rs b/src/diff/mod.rs index f3c2d8b..eb18e4b 100644 --- a/src/diff/mod.rs +++ b/src/diff/mod.rs @@ -46,8 +46,8 @@ where pub struct DiffOptions { compact: bool, context_len: usize, - original_filename: Option, - modified_filename: Option, + original_filename: Option>, + modified_filename: Option>, } impl DiffOptions { @@ -59,8 +59,8 @@ impl DiffOptions { Self { compact: true, context_len: 3, - original_filename: None, - modified_filename: None, + original_filename: Some("original".into()), + modified_filename: Some("modified".into()), } } @@ -83,16 +83,22 @@ impl DiffOptions { /// 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: impl ToString) -> &mut Self { - self.original_filename = Some(filename.to_string()); + 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: impl ToString) -> &mut Self { - self.modified_filename = Some(filename.to_string()); + pub fn set_modified_filename(&mut self, filename: T) -> &mut Self + where + T: Into>, + { + self.modified_filename = Some(filename.into()); self } @@ -123,16 +129,8 @@ impl DiffOptions { let hunks = to_hunks(&old_lines, &new_lines, &solution, self.context_len); Patch::new( - self.original_filename - .as_ref() - .map_or(Some(Cow::Borrowed("original")), |s| { - Some(Cow::Owned(s.clone())) - }), - self.modified_filename - .as_ref() - .map_or(Some(Cow::Borrowed("modified")), |s| { - Some(Cow::Owned(s.clone())) - }), + self.original_filename.clone(), + self.modified_filename.clone(), hunks, ) } @@ -150,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>( 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> {