difference/
lib.rs

1//! Functions to find the difference between two texts (strings).
2//! Usage
3//! ----------
4//!
5//! Add the following to your `Cargo.toml`:
6//!
7//! ```toml
8//! [dependencies]
9//! difference = "2.0"
10//! ```
11//!
12//! Now you can use the crate in your code
13//! ```ignore
14//! extern crate difference;
15//! ```
16//!
17//! ## Examples
18//!
19//! See [Examples.md](Examples.md) for more examples.
20//!
21//! ```rust
22//! use difference::{Difference, Changeset};
23//!
24//! let changeset = Changeset::new("test", "tent", "");
25//!
26//! assert_eq!(changeset.diffs, vec![
27//!   Difference::Same("te".to_string()),
28//!   Difference::Rem("s".to_string()),
29//!   Difference::Add("n".to_string()),
30//!   Difference::Same("t".to_string())
31//! ]);
32//! ```
33
34#![crate_name = "difference"]
35#![doc(html_root_url = "http://docs.rs/difference")]
36#![deny(missing_docs)]
37#![deny(warnings)]
38
39mod lcs;
40mod merge;
41mod display;
42
43use lcs::lcs;
44use merge::merge;
45
46/// Defines the contents of a changeset
47/// Changesets will be delivered in order of appearance in the original string
48/// Sequences of the same kind will be grouped into one Difference
49#[derive(PartialEq, Debug)]
50pub enum Difference {
51    /// Sequences that are the same
52    Same(String),
53    /// Sequences that are an addition (don't appear in the first string)
54    Add(String),
55    /// Sequences that are a removal (don't appear in the second string)
56    Rem(String),
57}
58
59/// The information about a full changeset
60pub struct Changeset {
61    /// An ordered vector of `Difference` objects, coresponding
62    /// to the differences within the text
63    pub diffs: Vec<Difference>,
64    /// The split used when creating the `Changeset`
65    /// Common splits are `""` for char-level, `" "` for word-level and `"\n"` for line-level.
66    pub split: String,
67    /// The edit distance of the `Changeset`
68    pub distance: i32,
69}
70
71impl Changeset {
72    /// Calculates the edit distance and the changeset for two given strings.
73    /// The first string is assumed to be the "original", the second to be an
74    /// edited version of the first. The third parameter specifies how to split
75    /// the input strings, leading to a more or less exact comparison.
76    ///
77    /// Common splits are `""` for char-level, `" "` for word-level and `"\n"` for line-level.
78    ///
79    /// Outputs the edit distance (how much the two strings differ) and a "changeset", that is
80    /// a `Vec` containing `Difference`s.
81    ///
82    /// # Examples
83    ///
84    /// ```
85    /// use difference::{Changeset, Difference};
86    ///
87    /// let changeset = Changeset::new("test", "tent", "");
88    ///
89    /// assert_eq!(changeset.diffs, vec![
90    ///     Difference::Same("te".to_string()),
91    ///     Difference::Rem("s".to_string()),
92    ///     Difference::Add("n".to_string()),
93    ///     Difference::Same("t".to_string())
94    /// ]);
95    /// ```
96    pub fn new(orig: &str, edit: &str, split: &str) -> Changeset {
97        let (dist, common) = lcs(orig, edit, split);
98        Changeset {
99            diffs: merge(orig, edit, &common, split),
100            split: split.to_string(),
101            distance: dist,
102        }
103    }
104}
105
106/// **This function is deprecated, please use `Changeset::new` instead**
107///
108/// Calculates the edit distance and the changeset for two given strings.
109/// The first string is assumed to be the "original", the second to be an
110/// edited version of the first. The third parameter specifies how to split
111/// the input strings, leading to a more or less exact comparison.
112///
113/// Common splits are `""` for char-level, `" "` for word-level and `"\n"` for line-level.
114///
115/// Outputs the edit distance (how much the two strings differ) and a "changeset", that is
116/// a `Vec` containing `Difference`s.
117///
118/// # Examples
119///
120/// ```
121/// use difference::diff;
122/// use difference::Difference;
123///
124/// let (dist, changeset) = diff("test", "tent", "");
125///
126/// assert_eq!(changeset, vec![
127///     Difference::Same("te".to_string()),
128///     Difference::Rem("s".to_string()),
129///     Difference::Add("n".to_string()),
130///     Difference::Same("t".to_string())
131/// ]);
132/// ```
133#[deprecated(since = "1.0.0", note = "please use `Changeset::new` instead")]
134pub fn diff(orig: &str, edit: &str, split: &str) -> (i32, Vec<Difference>) {
135    let ch = Changeset::new(orig, edit, split);
136    (ch.distance, ch.diffs)
137}
138
139/// Assert the difference between two strings. Works like diff, but takes
140/// a fourth parameter that is the expected edit distance (e.g. 0 if you want to
141/// test for equality).
142///
143/// To include this macro use:
144///
145/// ```
146/// #[macro_use(assert_diff)]
147/// extern crate difference;
148/// # fn main() { }
149/// ```
150///
151/// Remember that edit distance might not be equal to your understanding of difference,
152/// for example the words "Rust" and "Dust" have an edit distance of 2 because two changes (a
153/// removal and an addition) are required to make them look the same.
154///
155/// Will print an error with a colorful diff in case of failure.
156#[macro_export]
157macro_rules! assert_diff {
158    ($orig:expr , $edit:expr, $split: expr, $expected: expr) => ({
159        let orig = $orig;
160        let edit = $edit;
161
162        let changeset = $crate::Changeset::new(orig, edit, &($split));
163        if changeset.distance != $expected {
164            println!("{}", changeset);
165            panic!("assertion failed: edit distance between {:?} and {:?} is {} and not {}, see \
166                    diffset above",
167                   orig,
168                   edit,
169                   changeset.distance,
170                   &($expected))
171        }
172    })
173}
174
175/// **This function is deprecated, `Changeset` now implements the `Display` trait instead**
176///
177/// Prints a colorful visual representation of the diff.
178/// This is just a convenience function for those who want quick results.
179///
180/// I recommend checking out the examples on how to build your
181/// own diff output.
182/// # Examples
183///
184/// ```
185/// use difference::print_diff;
186/// print_diff("Diffs are awesome", "Diffs are cool", " ");
187/// ```
188#[deprecated(since = "1.0.0", note = "`Changeset` now implements the `Display` trait instead")]
189pub fn print_diff(orig: &str, edit: &str, split: &str) {
190    let ch = Changeset::new(orig, edit, split);
191    println!("{}", ch);
192}
193
194#[test]
195fn test_diff() {
196    let text1 = "Roses are red, violets are blue,\n\
197                 I wrote this library,\n\
198                 just for you.\n\
199                 (It's true).";
200
201    let text2 = "Roses are red, violets are blue,\n\
202                 I wrote this documentation,\n\
203                 just for you.\n\
204                 (It's quite true).";
205
206    let changeset = Changeset::new(text1, text2, "\n");
207
208    assert_eq!(changeset.distance, 4);
209
210    assert_eq!(
211        changeset.diffs,
212        vec![
213            Difference::Same("Roses are red, violets are blue,".to_string()),
214            Difference::Rem("I wrote this library,".to_string()),
215            Difference::Add("I wrote this documentation,".to_string()),
216            Difference::Same("just for you.".to_string()),
217            Difference::Rem("(It's true).".to_string()),
218            Difference::Add("(It's quite true).".to_string()),
219        ]
220    );
221}
222
223#[test]
224fn test_diff_brief() {
225    let text1 = "Hello\nworld";
226    let text2 = "Ola\nmundo";
227
228    let changeset = Changeset::new(text1, text2, "\n");
229
230    assert_eq!(
231        changeset.diffs,
232        vec![
233            Difference::Rem("Hello\nworld".to_string()),
234            Difference::Add("Ola\nmundo".to_string()),
235        ]
236    );
237}
238
239#[test]
240fn test_diff_smaller_line_count_on_left() {
241    let text1 = "Hello\nworld";
242    let text2 = "Ola\nworld\nHow is it\ngoing?";
243
244    let changeset = Changeset::new(text1, text2, "\n");
245
246    assert_eq!(
247        changeset.diffs,
248        vec![
249            Difference::Rem("Hello".to_string()),
250            Difference::Add("Ola".to_string()),
251            Difference::Same("world".to_string()),
252            Difference::Add("How is it\ngoing?".to_string()),
253        ]
254    );
255}
256
257#[test]
258fn test_diff_smaller_line_count_on_right() {
259    let text1 = "Hello\nworld\nWhat a \nbeautiful\nday!";
260    let text2 = "Ola\nworld";
261
262    let changeset = Changeset::new(text1, text2, "\n");
263
264    assert_eq!(
265        changeset.diffs,
266        vec![
267            Difference::Rem("Hello".to_string()),
268            Difference::Add("Ola".to_string()),
269            Difference::Same("world".to_string()),
270            Difference::Rem("What a \nbeautiful\nday!".to_string()),
271        ]
272    );
273}
274
275#[test]
276fn test_diff_similar_text_with_smaller_line_count_on_right() {
277    let text1 = "Hello\nworld\nWhat a \nbeautiful\nday!";
278    let text2 = "Hello\nwoRLd";
279
280    let changeset = Changeset::new(text1, text2, "\n");
281
282    assert_eq!(
283        changeset.diffs,
284        vec![
285            Difference::Same("Hello".to_string()),
286            Difference::Rem("world\nWhat a \nbeautiful\nday!".to_string()),
287            Difference::Add("woRLd".to_string()),
288        ]
289    );
290}
291
292#[test]
293fn test_diff_similar_text_with_similar_line_count() {
294    let text1 = "Hello\nworld\nWhat a \nbeautiful\nday!";
295    let text2 = "Hello\nwoRLd\nbeautiful";
296
297    let changeset = Changeset::new(text1, text2, "\n");
298
299    assert_eq!(
300        changeset.diffs,
301        vec![
302            Difference::Same("Hello".to_string()),
303            Difference::Rem("world\nWhat a ".to_string()),
304            Difference::Add("woRLd".to_string()),
305            Difference::Same("beautiful".to_string()),
306            Difference::Rem("day!".to_string()),
307        ]
308    );
309}
310
311#[test]
312#[should_panic]
313fn test_assert_diff_panic() {
314    let text1 = "Roses are red, violets are blue,\n\
315                 I wrote this library,\n\
316                 just for you.\n\
317                 (It's true).";
318
319    let text2 = "Roses are red, violets are blue,\n\
320                 I wrote this documentation,\n\
321                 just for you.\n\
322                 (It's quite true).";
323
324    assert_diff!(text1, text2, "\n'", 0);
325}
326
327#[test]
328fn test_assert_diff() {
329    let text1 = "Roses are red, violets are blue";
330
331    let text2 = "Roses are green, violets are blue";
332
333    assert_diff!(text1, text2, " ", 2);
334}