David's Blog

Building a Text Diff Tool in Rust

By David Li on Fri, 21 March 2024

Building a Text Diff Tool in Rust

In this tutorial, we will build a system tool in Rust that performs text diffing between two text files and outputs them nicely to the console. This tool will be similar to the diff command in Linux.

Step 1: Setup

The first step is to set up a new Rust project. We will be using the structopt and text_diff libraries for parsing command-line arguments and performing text diffing, respectively. Add the following lines to your Cargo.toml file:

[dependencies]
structopt = "0.3.21"
text_diff = "0.2.2"

The structopt library provides a convenient way to define and parse command-line arguments. The text_diff library provides a convenient way to perform text diffing.

Step 2: Define the Command-Line Arguments

Next, we need to define the command-line arguments for our tool. We will define two arguments, which are the paths to the two text files that we want to compare. Here is the code to define the arguments using the structopt library:

use std::path::PathBuf;
use structopt::StructOpt;

#[derive(StructOpt)]
struct Cli {
    #[structopt(parse(from_os_str))]
    file1: PathBuf,

    #[structopt(parse(from_os_str))]
    file2: PathBuf,
}

Step 3: Read the Files and Perform Text Diffing

Next, we need to read the two text files and perform text diffing. We will use the text_diff library for this. Here is the code to read the files and perform text diffing:

use std::fs::File;
use std::io::{BufRead, BufReader};
use text_diff::diff;

fn read_file(path: &std::path::PathBuf) -> Vec<String> {
    let file = File::open(path).expect("failed to open file");
    let reader = BufReader::new(file);
    reader.lines().map(|l| l.unwrap()).collect()
}

fn main() {
    let args = Cli::from_args();
    let file1_lines = read_file(&args.file1);
    let file2_lines = read_file(&args.file2);
    let comparison = diff(&file1_lines, &file2_lines, "\n");

    println!("{}", comparison);
}

The read_file function reads a file and returns its contents as a vector of lines. The main function uses Cli::from_args() to parse the command-line arguments. We then call read_file to read the contents of the two files. Finally, we use diff from the text_diff library to perform text diffing between the two files.

Step 4: Output the Results

Finally, we need to output the results of our text diffing to the console. Here is the code to output the results to the console:

use std::fmt;

impl fmt::Display for Change {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Change::Insert(s) => write!(f, "\x1b[32m{}\x1b[0m", s),
            Change::Delete(s) => write!(f, "\x1b[31m{}\x1b[0m", s),
            Change::Equal(s) => write!(f, "{}", s),
        }
    }
}

impl fmt::Display for TextDiff<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for change in self.iter_all_changes() {
            writeln!(f, "{}", change)?;
        }
        Ok(())
    }
}

We use impl fmt::Display to define how to display the changes between the two files. We use ANSI color codes to highlight insertions in green and deletions in red. We then use impl fmt::Display to define how to display the entire text diff.

Conclusion

In this tutorial, we have built a system tool in Rust that performs text diffing between two text files and outputs them nicely to the console. We have used the structopt and text_diff libraries to define and parse command-line arguments and perform text diffing. With this knowledge, you can build powerful system tools in Rust for a variety of tasks.

© Copyright 2024 by FriendlyUsers Tech Blog. Built with ♥ by FriendlyUser. Last updated on 2024-06-14.