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}