Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 25 additions & 9 deletions src/diff/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -57,6 +59,8 @@ impl DiffOptions {
Self {
compact: true,
context_len: 3,
original: "original",
modified: "modified",
}
}

Expand All @@ -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<Diff<'a, str>> {
fn diff(&self, original: &'a str, modified: &'a str) -> Vec<Diff<'a, str>> {
let solution = myers::diff(original.as_bytes(), modified.as_bytes());

let mut solution = solution
Expand All @@ -94,19 +110,19 @@ 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);

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],
Expand All @@ -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<T: PartialEq>(
&self,
old: &'a [T],
new: &'a [T],
Expand All @@ -136,7 +152,7 @@ impl DiffOptions {
}
}

impl Default for DiffOptions {
impl<'a> Default for DiffOptions<'a> {
fn default() -> Self {
Self::new()
}
Expand Down
51 changes: 51 additions & 0 deletions src/diff/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::{
apply::apply,
diff::{Diff, DiffRange},
patch::Patch,
PatchFormatter,
range::Range,
};

Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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 = "\
Expand Down
16 changes: 14 additions & 2 deletions src/patch/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use std::{
#[derive(Debug)]
pub struct PatchFormatter {
with_color: bool,
without_missing_newline_message: bool,

context: Style,
delete: Style,
Expand All @@ -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(),
Expand All @@ -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 }
Expand Down Expand Up @@ -238,7 +246,9 @@ impl<T: AsRef<[u8]> + ?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(())
Expand Down Expand Up @@ -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(())
Expand Down