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 
39 mod lcs;
40 mod merge;
41 mod display;
42 
43 use lcs::lcs;
44 use 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)]
50 pub 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
60 pub 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 
71 impl 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     /// ```
new(orig: &str, edit: &str, split: &str) -> Changeset96     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")]
diff(orig: &str, edit: &str, split: &str) -> (i32, Vec<Difference>)134 pub 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]
157 macro_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")]
print_diff(orig: &str, edit: &str, split: &str)189 pub fn print_diff(orig: &str, edit: &str, split: &str) {
190     let ch = Changeset::new(orig, edit, split);
191     println!("{}", ch);
192 }
193 
194 #[test]
test_diff()195 fn 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]
test_diff_brief()224 fn 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]
test_diff_smaller_line_count_on_left()240 fn 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]
test_diff_smaller_line_count_on_right()258 fn 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]
test_diff_similar_text_with_smaller_line_count_on_right()276 fn 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]
test_diff_similar_text_with_similar_line_count()293 fn 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]
test_assert_diff_panic()313 fn 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]
test_assert_diff()328 fn 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 }
335