diffy/lib.rs
1//! Tools for finding and manipulating differences between files
2//!
3//! ## Overview
4//!
5//! This library is intended to be a collection of tools used to find and
6//! manipulate differences between files inspired by [LibXDiff] and [GNU
7//! Diffutils]. Version control systems like [Git] and [Mercurial] generally
8//! communicate differences between two versions of a file using a `diff` or
9//! `patch`.
10//!
11//! The current diff implementation is based on the [Myers' diff algorithm].
12//!
13//! The documentation generally refers to "files" in many places but none of
14//! the apis explicitly operate on on-disk files. Instead this library
15//! requires that the text being operated on resides in-memory and as such if
16//! you want to perform operations on files, it is up to the user to load the
17//! contents of those files into memory before passing their contents to the
18//! apis provided by this library.
19//!
20//! ## UTF-8 and Non-UTF-8
21//!
22//! This library has support for working with both utf8 and non-utf8 texts.
23//! Most of the API's have two different variants, one for working with utf8
24//! `str` texts (e.g. [`create_patch`]) and one for working with bytes `[u8]`
25//! which may or may not be utf8 (e.g. [`create_patch_bytes`]).
26//!
27//! ## Creating a Patch
28//!
29//! A [`Patch`] between two texts can be created by doing the following:
30//!
31//! ```
32//! use diffy::create_patch;
33//!
34//! let original = "The Way of Kings\nWords of Radiance\n";
35//! let modified = "The Way of Kings\nWords of Radiance\nOathbringer\n";
36//!
37//! let patch = create_patch(original, modified);
38//! #
39//! # let expected = "\
40//! # --- original
41//! # +++ modified
42//! # @@ -1,2 +1,3 @@
43//! # The Way of Kings
44//! # Words of Radiance
45//! # +Oathbringer
46//! # ";
47//! #
48//! # assert_eq!(patch.to_string(), expected);
49//! ```
50//!
51//! A [`Patch`] can the be output in the [Unified Format] either by using its
52//! [`Display`] impl or by using a [`PatchFormatter`] to output the diff with
53//! color.
54//!
55//! ```
56//! # use diffy::create_patch;
57//! #
58//! # let original = "The Way of Kings\nWords of Radiance\n";
59//! # let modified = "The Way of Kings\nWords of Radiance\nOathbringer\n";
60//! #
61//! # let patch = create_patch(original, modified);
62//! #
63//! # let expected = "\
64//! # --- original
65//! # +++ modified
66//! # @@ -1,2 +1,3 @@
67//! # The Way of Kings
68//! # Words of Radiance
69//! # +Oathbringer
70//! # ";
71//! #
72//! # assert_eq!(patch.to_string(), expected);
73//! #
74//! // Without color
75//! print!("{}", patch);
76//!
77//! // With color
78//! # use diffy::PatchFormatter;
79//! let f = PatchFormatter::new().with_color();
80//! print!("{}", f.fmt_patch(&patch));
81//! ```
82//!
83//! ```console
84//! --- original
85//! +++ modified
86//! @@ -1,2 +1,3 @@
87//! The Way of Kings
88//! Words of Radiance
89//! +Oathbringer
90//! ```
91//!
92//! ## Applying a Patch
93//!
94//! Once you have a [`Patch`] you can apply it to a base image in order to
95//! recover the new text. Each hunk will be applied to the base image in
96//! sequence. Similarly to GNU `patch`, this implementation can detect when
97//! line numbers specified in the patch are incorrect and will attempt to find
98//! the correct place to apply each hunk by iterating forward and backward
99//! from the given position until all context lines from a hunk match the base
100//! image.
101//!
102//! ```
103//! use diffy::{apply, Patch};
104//!
105//! let s = "\
106//! --- a/skybreaker-ideals
107//! +++ b/skybreaker-ideals
108//! @@ -10,6 +10,8 @@
109//! First:
110//! Life before death,
111//! strength before weakness,
112//! journey before destination.
113//! Second:
114//! - I will put the law before all else.
115//! + I swear to seek justice,
116//! + to let it guide me,
117//! + until I find a more perfect Ideal.
118//! ";
119//!
120//! let patch = Patch::from_str(s).unwrap();
121//!
122//! let base_image = "\
123//! First:
124//! Life before death,
125//! strength before weakness,
126//! journey before destination.
127//! Second:
128//! I will put the law before all else.
129//! ";
130//!
131//! let expected = "\
132//! First:
133//! Life before death,
134//! strength before weakness,
135//! journey before destination.
136//! Second:
137//! I swear to seek justice,
138//! to let it guide me,
139//! until I find a more perfect Ideal.
140//! ";
141//!
142//! assert_eq!(apply(base_image, &patch).unwrap(), expected);
143//! ```
144//!
145//! ## Performing a Three-way Merge
146//!
147//! Two files `A` and `B` can be merged together given a common ancestor or
148//! original file `O` to produce a file `C` similarly to how [diff3]
149//! performs a three-way merge.
150//!
151//! ```console
152//! --- A ---
153//! / \
154//! / \
155//! O C
156//! \ /
157//! \ /
158//! --- B ---
159//! ```
160//!
161//! If files `A` and `B` modified different regions of the original file `O`
162//! (or the same region in the same way) then the files can be merged without
163//! conflict.
164//!
165//! ```
166//! use diffy::merge;
167//!
168//! let original = "the final empire\nThe Well of Ascension\nThe hero of ages\n";
169//! let a = "The Final Empire\nThe Well of Ascension\nThe Hero of Ages\n";
170//! let b = "The Final Empire\nThe Well of Ascension\nThe hero of ages\n";
171//! let expected = "\
172//! The Final Empire
173//! The Well of Ascension
174//! The Hero of Ages
175//! ";
176//!
177//! assert_eq!(merge(original, a, b).unwrap(), expected);
178//! ```
179//!
180//! If both files `A` and `B` modified the same region of the original file
181//! `O` (and those modifications are different), it would result in a conflict
182//! as it is not clear which modifications should be used in the merged
183//! result.
184//!
185//! ```
186//! use diffy::merge;
187//!
188//! let original = "The Final Empire\nThe Well of Ascension\nThe hero of ages\n";
189//! let a = "The Final Empire\nThe Well of Ascension\nThe Hero of Ages\nSecret History\n";
190//! let b = "The Final Empire\nThe Well of Ascension\nThe hero of ages\nThe Alloy of Law\n";
191//! let expected = "\
192//! The Final Empire
193//! The Well of Ascension
194//! <<<<<<< ours
195//! The Hero of Ages
196//! Secret History
197//! ||||||| original
198//! The hero of ages
199//! =======
200//! The hero of ages
201//! The Alloy of Law
202//! >>>>>>> theirs
203//! ";
204//!
205//! assert_eq!(merge(original, a, b).unwrap_err(), expected);
206//! ```
207//!
208//! [LibXDiff]: http://www.xmailserver.org/xdiff-lib.html
209//! [Myers' diff algorithm]: http://www.xmailserver.org/diff2.pdf
210//! [GNU Diffutils]: https://www.gnu.org/software/diffutils/
211//! [Git]: https://git-scm.com/
212//! [Mercurial]: https://www.mercurial-scm.org/
213//! [Unified Format]: https://en.wikipedia.org/wiki/Diff#Unified_format
214//! [diff3]: https://en.wikipedia.org/wiki/Diff3
215//!
216//! [`Display`]: https://doc.rust-lang.org/stable/std/fmt/trait.Display.html
217//! [`Patch`]: struct.Patch.html
218//! [`PatchFormatter`]: struct.PatchFormatter.html
219//! [`create_patch`]: fn.create_patch.html
220//! [`create_patch_bytes`]: fn.create_patch_bytes.html
221
222mod apply;
223mod diff;
224mod merge;
225mod patch;
226mod range;
227mod utils;
228
229pub use apply::{apply, apply_bytes, ApplyError};
230pub use diff::{create_patch, create_patch_bytes, DiffOptions};
231pub use merge::{merge, merge_bytes, ConflictStyle, MergeOptions};
232pub use patch::{Hunk, HunkRange, Line, ParsePatchError, Patch, PatchFormatter};