Diffing & Patching

RecipeCratesCategories
diffdiffcat-text-processing
similarsimilarcat-text-processing

diff

diff diff-crates.io diff-github diff-lib.rs

diff is an LCS-based slice and string diffing implementation.

use diff::Result;

// Add to your `Cargo.toml`:
// [dependencies]
// diff = "0.1.13" # Or latest

fn main() -> anyhow::Result<()> {
    let old_text = "Hello world\nThis is a test\nOf the diff library";
    let new_text =
        "Hello world\nThis is a sample\nOf the diff library\nWith a new line";

    // Computes the diff between the lines of two strings.
    let changeset = diff::lines(old_text, new_text);

    // Print the diff
    for chunk in changeset {
        match chunk {
            Result::Both(s, _) => println!(" {}", s),
            Result::Left(s) => println!("-{}", s),
            Result::Right(s) => println!("+{}", s),
        }
    }

    Ok(())
}

similar

similar similar-crates.io similar-github similar-lib.rs

similar is a dependency-free crate for Rust that implements different diffing algorithms and high level interfaces for it.

use std::fmt;

use console::Style;
use similar::ChangeTag;
use similar::TextDiff;

// Add to your `Cargo.toml`:
// [dependencies]
// similar = "2.7.0" # Or latest
// console = "0.15.11" # Or latest

struct Line(Option<usize>, String);

impl fmt::Display for Line {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.0 {
            None => write!(f, "    {}", self.1),
            Some(idx) => write!(f, "{:4} {}", idx + 1, self.1),
        }
    }
}

fn line_diff() {
    // Original and modified text
    let old_text = "Hello World\nThis is a test\nfor the `similar` crate\nto demonstrate diffing";
    let new_text = "Hello Rust\nThis is a test\nusing the `similar` crate\nto demonstrate diffing\nwith added line";

    // Generate a diff with context
    let diff = TextDiff::from_lines(old_text, new_text);

    // Define styles for different types of changes
    let add_style = Style::new().green();
    let remove_style = Style::new().red();
    let unchanged_style = Style::new();

    // Print the styled diff
    println!("Text Difference:");
    for change in diff.iter_all_changes() {
        let (sign, style) = match change.tag() {
            ChangeTag::Delete => ("-", &remove_style),
            ChangeTag::Insert => ("+", &add_style),
            ChangeTag::Equal => (" ", &unchanged_style),
        };

        // Format the line with line number and styling
        let line = Line(
            change.old_index().or(change.new_index()),
            format!("{}{}", sign, change.value()),
        );
        println!("{}", style.apply_to(line));
    }

    // Use the unified_diff format for comparison
    println!("\nUnified Diff Format:");
    let mut unified = diff.unified_diff();
    let unified = unified.context_radius(2).header("old_file", "new_file");

    print!("{}", unified);
}

// Creates a diff of words.
// This splits the text into words and whitespace.
fn word_diff() {
    let text1 = "The quick brown fox jumps over the lazy dog";
    let text2 = "The quick red fox jumps over the sleepy dog";

    let diff = TextDiff::from_words(text1, text2);

    print!("\nWord-by-word changes: ");
    for change in diff.iter_all_changes() {
        match change.tag() {
            ChangeTag::Delete => print!("\x1b[31m{}\x1b[0m", change.value()),
            ChangeTag::Insert => print!("\x1b[32m{}\x1b[0m", change.value()),
            ChangeTag::Equal => print!("{}", change.value()),
        }
    }
    println!();
}

fn main() {
    line_diff();
    word_diff();
}

Related Topics

  • Development Tools.
  • Strings.