Skip to content

Commit

Permalink
Merge 08be11b into c5749ad
Browse files Browse the repository at this point in the history
  • Loading branch information
stegmanh committed Apr 7, 2017
2 parents c5749ad + 08be11b commit 5279dd7
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 23 deletions.
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())]);
}

0 comments on commit 5279dd7

Please sign in to comment.