text_diff/
lib.rs

1//! Functions to find the difference between to texts (strings).
2//! Usage
3//! ----------
4//!
5//! Add the following to your Cargo.toml:
6//!
7//! ```toml
8//! [dependencies.text_diff]
9//!
10//! git = "https://github.com/johannhof/text-diff.rs.git"
11//! ```
12//!
13//! Now you can use the crate in your code
14//!
15//! ```ignore
16//! extern crate text_diff;
17//! ```
18
19#![crate_name = "text_diff"]
20#![doc(html_root_url = "https://johannhof.github.io/text-diff.rs/")]
21
22// I can basically feel the karma already
23#![deny(missing_docs)]
24
25mod lcs;
26mod merge;
27
28use lcs::lcs;
29use merge::merge;
30
31/// Defines the contents of a changeset
32/// Changesets will be delivered in order of appearance in the original string
33/// Sequences of the same kind will be grouped into one Difference
34#[derive(PartialEq, Debug)]
35pub enum Difference {
36    /// Sequences that are the same
37    Same(String),
38    /// Sequences that are an addition (don't appear in the first string)
39    Add(String),
40    /// Sequences that are a removal (don't appear in the second string)
41    Rem(String)
42}
43
44/// Calculates the edit distance and the changeset for two given strings.
45/// The first string is assumed to be the "original", the second to be an
46/// edited version of the first. The third parameter specifies how to split
47/// the input strings, leading to a more or less exact comparison.
48///
49/// Common splits are `""` for char-level, `" "` for word-level and `"\n"` for line-level.
50///
51/// Outputs the edit distance (how much the two strings differ) and a "changeset", that is
52/// a `Vec` containing `Difference`s.
53///
54/// # Examples
55///
56/// ```
57/// use text_diff::diff;
58/// use text_diff::Difference;
59///
60/// let (dist, changeset) = diff("test", "tent", "");
61///
62/// assert_eq!(changeset, vec![
63///     Difference::Same("te".to_string()),
64///     Difference::Rem("s".to_string()),
65///     Difference::Add("n".to_string()),
66///     Difference::Same("t".to_string())
67/// ]);
68/// ```
69pub fn diff(orig: &str, edit: &str, split: &str) -> (i32, Vec<Difference>) {
70    let (dist, common) = lcs(orig, edit, split);
71    (dist, merge(orig, edit, &common, split))
72}
73
74/// Assert the difference between two strings. Works like diff, but takes
75/// a fourth parameter that is the expected edit distance (e.g. 0 if you want to
76/// test for equality).
77///
78/// Remember that edit distance might not be equal to your understanding of difference,
79/// for example the words "Rust" and "Dust" have an edit distance of 2 because two changes (a
80/// removal and an addition) are required to make them look the same.
81///
82/// Will print an error with a colorful diff using print_diff in case of failure.
83pub fn assert_diff(orig: &str, edit: &str, split: &str, expected: i32) {
84    let (d, _) = diff(orig, edit, split);
85    if d != expected {
86        print_diff(orig, edit, split);
87        panic!("assertion failed: edit distance between {:?} and {:?} is {} and not {}, see diffset above"
88               , orig, edit, d, expected)
89    }
90}
91
92/// Prints a colorful visual representation of the diff.
93/// This is just a convenience function for those who want quick results.
94///
95/// I recommend checking out the examples on how to build your
96/// own diff output.
97/// # Examples
98///
99/// ```
100/// use text_diff::print_diff;
101/// print_diff("Diffs are awesome", "Diffs are cool", " ");
102/// ```
103pub fn print_diff(orig: &str, edit: &str, split: &str) {
104    let (_, changeset) = diff(orig, edit, split);
105    let mut ret = String::new();
106
107    for seq in changeset {
108        match seq {
109            Difference::Same(ref x) => {
110                ret.push_str(x);
111                ret.push_str(split);
112            },
113            Difference::Add(ref x) => {
114                ret.push_str("\x1B[92m");
115                ret.push_str(x);
116                ret.push_str("\x1B[0m");
117                ret.push_str(split);
118            },
119            Difference::Rem(ref x) => {
120                ret.push_str("\x1B[91m");
121                ret.push_str(x);
122                ret.push_str("\x1B[0m");
123                ret.push_str(split);
124            }
125        }
126    }
127    println!("{}", ret);
128}
129
130#[test]
131fn test_diff() {
132    let text1 = "Roses are red, violets are blue,\n\
133                 I wrote this library,\n\
134                 just for you.\n\
135                 (It's true).";
136
137    let text2 = "Roses are red, violets are blue,\n\
138                 I wrote this documentation,\n\
139                 just for you.\n\
140                 (It's quite true).";
141
142    let (dist, changeset) = diff(text1, text2, "\n");
143
144    assert_eq!(dist, 4);
145
146    assert_eq!(changeset, vec![
147         Difference::Same("Roses are red, violets are blue,".to_string()),
148         Difference::Rem("I wrote this library,".to_string()),
149         Difference::Add("I wrote this documentation,".to_string()),
150         Difference::Same("just for you.".to_string()),
151         Difference::Rem("(It's true).".to_string()),
152         Difference::Add("(It's quite true).".to_string())
153    ]);
154}
155
156#[test]
157#[should_panic]
158fn test_assert_diff_panic() {
159    let text1 = "Roses are red, violets are blue,\n\
160                 I wrote this library,\n\
161                 just for you.\n\
162                 (It's true).";
163
164    let text2 = "Roses are red, violets are blue,\n\
165                 I wrote this documentation,\n\
166                 just for you.\n\
167                 (It's quite true).";
168
169    assert_diff(text1, text2, "\n'", 0);
170}
171
172#[test]
173fn test_assert_diff() {
174    let text1 = "Roses are red, violets are blue";
175
176    let text2 = "Roses are green, violets are blue";
177
178    assert_diff(text1, text2, " ", 2);
179}