Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ bin = ["getopts"]

[dependencies]
getopts = {version = "0.2", optional = true}
term = "0.2.7"

[dev-dependencies]
term = "0.2.7"
17 changes: 8 additions & 9 deletions examples/line-by-line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@ extern crate difference;
use difference::{Difference, Changeset};
use std::io::Write;

/*
* The only thing to do here is to create a diff based on line
* splits (passing the newline character as a split symbol)
* and iterate over the results, matching and formatting them based
* on the type of `Difference`.
*
* Screenshot:
* https://raw.githubusercontent.com/johannhof/difference.rs/master/assets/git-style.png
*/
// The only thing to do here is to create a diff based on line
// splits (passing the newline character as a split symbol)
// and iterate over the results, matching and formatting them based
// on the type of `Difference`.
//
// Screenshot:
// https://raw.githubusercontent.com/johannhof/difference.rs/master/assets/git-style.png
//

#[allow(unused_must_use)]
#[cfg_attr(feature = "cargo-clippy", allow(needless_range_loop))]
Expand Down
36 changes: 34 additions & 2 deletions src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,18 @@ impl fmt::Display for Changeset {
try!(write!(f, "{}{}", x, self.split));
}
Difference::Add(ref x) => {
try!(write!(f, "\x1b[92m{}\x1b[0m{}", x, self.split));
if self.word_diff {
try!(write!(f, "[+{}+]{}", x, self.split));
} else {
try!(write!(f, "\x1b[92m{}\x1b[0m{}", x, self.split));
}
}
Difference::Rem(ref x) => {
try!(write!(f, "\x1b[91m{}\x1b[0m{}", x, self.split));
if self.word_diff {
try!(write!(f, "[-{}-]{}", x, self.split));
} else {
try!(write!(f, "\x1b[91m{}\x1b[0m{}", x, self.split));
}
}
}
}
Expand All @@ -25,6 +33,7 @@ impl fmt::Display for Changeset {
#[cfg(test)]
mod tests {
use super::super::Changeset;
use super::super::ChangesetOptions;
use std::io::Write;
use std::iter::FromIterator;
use std::thread;
Expand Down Expand Up @@ -97,4 +106,27 @@ mod tests {
assert_eq!(result, vb(expected));

}

#[test]
fn test_display_with_word_diff() {
let text1 = "Roses are red, violets are blue,\n\
I wrote this library,\n\
just for you.\n\
(It's true).";

let text2 = "Roses are red, violets are blue,\n\
I wrote this documentation,\n\
just for you.\n\
(It's quite true).";
let expected = b"Roses are red, violets are blue,\n[-I wrote this library,-]\
\n[+I wrote this documentation,+]\njust for you.\n[-\
(It's true).-]\n[+(It's quite true).+]\n";

let ch_options = ChangesetOptions::new(true);
let ch = Changeset::new_with_options(text1, text2, "\n", ch_options);
let mut result: Vec<u8> = Vec::new();
write!(result, "{}", ch).unwrap();
debug_bytes(&result, expected);
assert_eq!(result, vb(expected));
}
}
116 changes: 108 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,15 @@
#![deny(missing_docs)]
#![deny(warnings)]

extern crate term;

mod lcs;
mod merge;
mod display;

use lcs::lcs;
use merge::merge;
use std::io::prelude::*;

/// Defines the contents of a changeset
/// Changesets will be delivered in order of appearance in the original string
Expand All @@ -56,6 +59,21 @@ pub enum Difference {
Rem(String),
}

/// Struct to hold additional information about producing diff
pub struct ChangesetOptions {
/// Display output of diff using words instead of colors
/// # Example
/// [-g-][+f+]oo
pub word_diff: bool,
}

impl ChangesetOptions {
/// Returns a new ChangesetOptions with parameters
pub fn new(word_diff: bool) -> ChangesetOptions {
ChangesetOptions { word_diff: word_diff }
}
}

/// The information about a full changeset
pub struct Changeset {
/// An ordered vector of `Difference` objects, coresponding
Expand All @@ -66,6 +84,8 @@ pub struct Changeset {
pub split: String,
/// The edit distance of the `Changeset`
pub distance: i32,
/// Determines useage of words instead of color for diffs
pub word_diff: bool,
}

impl Changeset {
Expand Down Expand Up @@ -99,6 +119,49 @@ impl Changeset {
diffs: merge(orig, edit, &common, split),
split: split.to_string(),
distance: dist,
word_diff: false,
}
}

/// Calculates the edit distance and the changeset for two given strings.
/// The first string is assumed to be the "original", the second to be an
/// edited version of the first. The third parameter specifies how to split
/// the input strings, leading to a more or less exact comparison.
///
/// Common splits are `""` for char-level, `" "` for word-level and `"\n"` for line-level.
///
/// Outputs the edit distance (how much the two strings differ) and a "changeset", that is
/// a `Vec` containing `Difference`s.
///
/// This function allows a ChangesetOptions struct to be passed - tuning how the diffs are
/// produced & displayed
///
/// # Examples
///
/// ```
/// use difference::{Changeset, ChangesetOptions, Difference};
///
/// let changeset_options = ChangesetOptions::new(true);
/// let changeset = Changeset::new_with_options("test", "tent", "", changeset_options);
///
/// assert_eq!(changeset.diffs, vec![
/// Difference::Same("te".to_string()),
/// Difference::Rem("s".to_string()),
/// Difference::Add("n".to_string()),
/// Difference::Same("t".to_string())
/// ]);
/// ```
pub fn new_with_options(orig: &str,
edit: &str,
split: &str,
options: ChangesetOptions)
-> Changeset {
let (dist, common) = lcs(orig, edit, split);
Changeset {
diffs: merge(orig, edit, &common, split),
split: split.to_string(),
distance: dist,
word_diff: options.word_diff,
}
}
}
Expand Down Expand Up @@ -172,23 +235,60 @@ macro_rules! assert_diff {
})
}

/// **This function is deprecated, `Changeset` now implements the `Display` trait instead**
/// Prints a colorful visual representation of the diff to standard out.
/// This is a convenience function for printing colored diff results.
///
/// Prints a colorful visual representation of the diff.
/// This is just a convenience function for those who want quick results.
/// The difference between this & the display impl is this uses the Term crate for colors,
/// allowing colors to appear in windows terminals
///
/// I recommend checking out the examples on how to build your
/// own diff output.
/// # Examples
///
/// ```
/// use difference::print_diff;
/// print_diff("Diffs are awesome", "Diffs are cool", " ");
///
/// let changeset_options = difference::ChangesetOptions::new(false);
/// print_diff("Diffs are awesome", "Diffs are cool", " ", changeset_options);
/// ```
#[deprecated(since="1.0.0", note="`Changeset` now implements the `Display` trait instead")]
pub fn print_diff(orig: &str, edit: &str, split: &str) {
let ch = Changeset::new(orig, edit, split);
println!("{}", ch);
pub fn print_diff(orig: &str,
edit: &str,
split: &str,
options: ChangesetOptions)
-> Result<(), std::io::Error> {
let ch = Changeset::new_with_options(orig, edit, split, options);
let mut t = term::stdout().unwrap();

for d in &ch.diffs {
t.reset().unwrap();
if ch.word_diff {
match *d {
Difference::Same(ref x) => try!(write!(t, "{}{}", x, ch.split)),
Difference::Add(ref x) => try!(write!(t, "[-{}-]{}", x, ch.split)),
Difference::Rem(ref x) => try!(write!(t, "[+{}+]{}", x, ch.split)),
};
} else {
match *d {
Difference::Same(ref x) => {
try!(write!(t, "{}{}", x, ch.split));
}
Difference::Add(ref x) => {
t.fg(term::color::GREEN).unwrap();
try!(write!(t, "{}{}", x, ch.split));
}
Difference::Rem(ref x) => {
t.fg(term::color::RED).unwrap();
try!(write!(t, "{}{}", x, ch.split));
}
};
}
}
t.reset().unwrap();
if !split.contains("\n") {
// Include trailing line break if split doesn't include one
try!(writeln!(t, ""));
}
Ok(())
}

#[test]
Expand Down
9 changes: 7 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ fn main() {

let mut opts = Options::new();
opts.optopt("s", "split", "", "char|word|line");
opts.optflag("",
"word-diff",
"Enable word diffs instead of colored diffs");
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(f) => panic!(f.to_string()),
Expand All @@ -33,9 +36,11 @@ fn main() {
_ => " ",
};

let changeset_options = difference::ChangesetOptions::new(matches.opt_present("word-diff"));

if matches.free.len() > 1 {
let ch = difference::Changeset::new(&matches.free[0], &matches.free[1], split);
println!("{}", ch);
difference::print_diff(&matches.free[0], &matches.free[1], split, changeset_options)
.unwrap();
} else {
print!("{}", opts.usage(&format!("Usage: {} [options]", program)));
return;
Expand Down
19 changes: 18 additions & 1 deletion src/merge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,28 @@ pub fn merge(orig: &str, edit: &str, common: &str, split: &str) -> Vec<Differenc


#[test]
fn test_merge() {
fn test_merge_char() {
assert_eq!(merge("testa", "tost", "tst", ""),
vec![Difference::Same("t".to_string()),
Difference::Rem("e".to_string()),
Difference::Add("o".to_string()),
Difference::Same("st".to_string()),
Difference::Rem("a".to_string())]);
}

#[test]
fn test_merge_word() {
assert_eq!(merge("foo bar", "boo bar", "bar", " "),
vec![Difference::Same("".to_string()),
Difference::Rem("foo".to_string()),
Difference::Add("boo".to_string()),
Difference::Same("bar".to_string())]);
}

#[test]
fn test_merge_line() {
assert_eq!(merge("foo", "boo", "", "\n"),
vec![Difference::Same("".to_string()),
Difference::Rem("foo".to_string()),
Difference::Add("boo".to_string())]);
}