differ 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,99 @@
1
+ = Differ
2
+
3
+ As streams of text swirled before the young man's eyes, his mind swam with
4
+ thoughts of many things. They would have to wait, however, as he focussed his
5
+ full concentration on the shifting patterns ahead of him. A glint of light
6
+ reflecting off a piece of buried code caught his eye and any hope he had was
7
+ lost. For the very moment he glanced aside, the landscape became Different.
8
+ The young man gave a small sigh and trudged onward in solemn resignation,
9
+ fated to wander the desolate codebanks in perpetuity.
10
+
11
+ Differ is a flexible, pure-Ruby diff library, suitable for use in both command
12
+ line scripts and web applications. The flexibility comes from the fact that
13
+ diffs can be built at completely arbitrary levels of granularity (some common
14
+ ones are built-in), and can be output in a variety of formats.
15
+
16
+ == Installation
17
+
18
+ sudo gem install differ
19
+
20
+ == Usage
21
+
22
+ There are a number of ways to use Differ, depending on your situation and needs.
23
+
24
+ @original = "Epic lolcat fail!"
25
+ @current = "Epic wolfman fail!"
26
+
27
+ You can call the Differ module directly.
28
+
29
+ require 'differ'
30
+
31
+ There are a number of built-in diff methods to choose from...
32
+
33
+ @diff = Differ.diff_by_line(@current, @original)
34
+ # => "{"Epic lolcat fail!" >> "Epic wolfman fail!"}"
35
+
36
+ @diff = Differ.diff_by_word(@current, @original)
37
+ # => "Epic {"lolcat" >> "wolfman"} fail!"
38
+
39
+ @diff = Differ.diff_by_char(@current, @original)
40
+ # => "Epic {+"wo"}l{-"olcat "}f{+"m"}a{+"n fa"}il!"
41
+
42
+ ... or call #diff directly and supply your own boundary string!
43
+
44
+ @diff = Differ.diff(@current, @original) # implicitly by line!
45
+ # => "{"Epic lolcat fail!" >> "Epic wolfman fail!"}"
46
+
47
+ @diff = Differ.diff(@current, @original, 'i')
48
+ # => "Epi{"c lolcat fa" >> "c wolfman fa"}il"
49
+
50
+ If you would like something a little more inline...
51
+
52
+ require 'differ/string'
53
+
54
+ @diff = @current.diff(@original) # implicitly by line!
55
+ # => "{"Epic lolcat fail!" >> "Epic wolfman fail!"}"
56
+
57
+ ... or a lot more inline...
58
+
59
+ @diff = (@current - @original) # implicitly by line!
60
+ # => "{"Epic lolcat fail!" >> "Epic wolfman fail!"}"
61
+
62
+ $; = ' '
63
+ @diff = (@current - @original)
64
+ # => "Epic {"lolcat" >> "wolfman"} fail!"
65
+
66
+ ... we've pretty much got you covered.
67
+
68
+ === Output Formatting
69
+
70
+ Need a different output format? We've got a few of those too.
71
+
72
+ Differ.format = :ascii # <- Default
73
+ Differ.format = :color
74
+ Differ.format = :html
75
+
76
+ Differ.format = MyCustomFormatModule
77
+
78
+ Don't want to change the system-wide default for only a single diff output?
79
+ Yeah, me either.
80
+
81
+ @diff = (@current - @original)
82
+ @diff.format_as(:color)
83
+
84
+ == Copyright
85
+
86
+ Copyright (c) 2009 Pieter Vande Bruggen.
87
+
88
+ (The GIFT License, v1)
89
+
90
+ Permission is hereby granted to use this software and/or its source code for
91
+ whatever purpose you should choose. Seriously, go nuts. Use it for your personal
92
+ RSS feed reader, your wildly profitable social network, or your mission to Mars.
93
+
94
+ I don't care, it's yours. Change the name on it if you want -- in fact, if you
95
+ start significantly changing what it does, I'd rather you did! Make it your own
96
+ little work of art, complete with a stylish flowing signature in the corner. All
97
+ I really did was give you the canvas. And my blessing.
98
+
99
+ Know always right from wrong, and let others see your good works.
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 1
3
+ :major: 0
4
+ :minor: 1
data/lib/differ.rb ADDED
@@ -0,0 +1,83 @@
1
+ require 'differ/change'
2
+ require 'differ/diff'
3
+ require 'differ/format/ascii'
4
+ require 'differ/format/color'
5
+ require 'differ/format/html'
6
+
7
+ module Differ
8
+ class << self
9
+
10
+ def diff(target, source, separator = "\n")
11
+ old_sep, $; = $;, separator
12
+
13
+ target = target.split(separator)
14
+ source = source.split(separator)
15
+
16
+ $; = '' if separator.is_a? Regexp
17
+
18
+ @diff = Diff.new
19
+ advance(target, source) until source.empty? || target.empty?
20
+ @diff.insert(*target) || @diff.delete(*source)
21
+ return @diff
22
+ ensure
23
+ $; = old_sep
24
+ end
25
+
26
+ def diff_by_char(to, from)
27
+ diff(to, from, '')
28
+ end
29
+
30
+ def diff_by_word(to, from)
31
+ diff(to, from, /\b/)
32
+ end
33
+
34
+ def diff_by_line(to, from)
35
+ diff(to, from, "\n")
36
+ end
37
+
38
+ def format=(f)
39
+ @format = format_for(f)
40
+ end
41
+
42
+ def format
43
+ return @format || Format::Ascii
44
+ end
45
+
46
+ def format_for(f)
47
+ case f
48
+ when Module then f
49
+ when :ascii then Format::Ascii
50
+ when :color then Format::Color
51
+ when :html then Format::HTML
52
+ when nil then nil
53
+ else raise "Unknown format type #{f.inspect}"
54
+ end
55
+ end
56
+
57
+ private
58
+ def advance(target, source)
59
+ del, add = source.shift, target.shift
60
+
61
+ prioritize_insert = target.length > source.length
62
+ insert = target.index(del)
63
+ delete = source.index(add)
64
+
65
+ if del == add
66
+ @diff.same(add)
67
+ elsif insert && prioritize_insert
68
+ change(:insert, target.unshift(add), insert)
69
+ elsif delete
70
+ change(:delete, source.unshift(del), delete)
71
+ elsif insert && !prioritize_insert
72
+ change(:insert, target.unshift(add), insert)
73
+ else
74
+ @diff.insert(add) && @diff.delete(del)
75
+ end
76
+ end
77
+
78
+ def change(method, array, index)
79
+ @diff.send(method, *array.slice!(0..index))
80
+ @diff.same(array.shift)
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,30 @@
1
+ module Differ
2
+ class Change # :nodoc:
3
+ attr_accessor :insert, :delete
4
+ def initialize(options = {})
5
+ @insert = options[:insert] || ''
6
+ @delete = options[:delete] || ''
7
+ end
8
+
9
+ def insert?
10
+ !@insert.empty?
11
+ end
12
+
13
+ def delete?
14
+ !@delete.empty?
15
+ end
16
+
17
+ def change?
18
+ !@insert.empty? && !@delete.empty?
19
+ end
20
+
21
+ def to_s
22
+ Differ.format.format(self)
23
+ end
24
+ alias :inspect :to_s
25
+
26
+ def ==(other)
27
+ self.insert == other.insert && self.delete == other.delete
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,95 @@
1
+ module Differ
2
+ class Diff
3
+ def initialize
4
+ @raw = []
5
+ end
6
+
7
+ def same(*str)
8
+ return if str.empty?
9
+ if @raw.last.is_a? String
10
+ @raw.last << sep
11
+ elsif @raw.last.is_a? Change
12
+ if @raw.last.change?
13
+ @raw << sep
14
+ else
15
+ change = @raw.pop
16
+ if change.insert? && @raw.last
17
+ @raw.last << sep if change.insert.sub!(/^#{Regexp.quote(sep)}/, '')
18
+ end
19
+ if change.delete? && @raw.last
20
+ @raw.last << sep if change.delete.sub!(/^#{Regexp.quote(sep)}/, '')
21
+ end
22
+ @raw << change
23
+
24
+ @raw.last.insert << sep if @raw.last.insert?
25
+ @raw.last.delete << sep if @raw.last.delete?
26
+ @raw << ''
27
+ end
28
+ else
29
+ @raw << ''
30
+ end
31
+ @raw.last << str.join(sep)
32
+ end
33
+
34
+ def delete(*str)
35
+ return if str.empty?
36
+ if @raw.last.is_a? Change
37
+ change = @raw.pop
38
+ if change.insert? && @raw.last
39
+ @raw.last << sep if change.insert.sub!(/^#{Regexp.quote(sep)}/, '')
40
+ end
41
+ change.delete << sep if change.delete?
42
+ else
43
+ change = Change.new(:delete => @raw.empty? ? '' : sep)
44
+ end
45
+
46
+ @raw << change
47
+ @raw.last.delete << str.join(sep)
48
+ end
49
+
50
+ def insert(*str)
51
+ return if str.empty?
52
+ if @raw.last.is_a? Change
53
+ change = @raw.pop
54
+ if change.delete? && @raw.last
55
+ @raw.last << sep if change.delete.sub!(/^#{Regexp.quote(sep)}/, '')
56
+ end
57
+ change.insert << sep if change.insert?
58
+ else
59
+ change = Change.new(:insert => @raw.empty? ? '' : sep)
60
+ end
61
+
62
+ @raw << change
63
+ @raw.last.insert << str.join(sep)
64
+ end
65
+
66
+ def ==(other)
67
+ @raw == other.raw_array
68
+ end
69
+
70
+ def to_s
71
+ @raw.to_s
72
+ end
73
+
74
+ def format_as(f)
75
+ f = Differ.format_for(f)
76
+ @raw.inject('') do |sum, part|
77
+ part = case part
78
+ when String then part
79
+ when Change then f.format(part)
80
+ end
81
+ sum << part
82
+ end
83
+ end
84
+
85
+ protected
86
+ def raw_array
87
+ @raw
88
+ end
89
+
90
+ private
91
+ def sep
92
+ "#{$;}"
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,27 @@
1
+ module Differ
2
+ module Format
3
+ module Ascii
4
+ class << self
5
+ def format(change)
6
+ (change.change? && as_change(change)) ||
7
+ (change.delete? && as_delete(change)) ||
8
+ (change.insert? && as_insert(change)) ||
9
+ ''
10
+ end
11
+
12
+ private
13
+ def as_insert(change)
14
+ "{+#{change.insert.inspect}}"
15
+ end
16
+
17
+ def as_delete(change)
18
+ "{-#{change.delete.inspect}}"
19
+ end
20
+
21
+ def as_change(change)
22
+ "{#{change.delete.inspect} >> #{change.insert.inspect}}"
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ module Differ
2
+ module Format
3
+ module Color
4
+ class << self
5
+ def format(change)
6
+ (change.change? && as_change(change)) ||
7
+ (change.delete? && as_delete(change)) ||
8
+ (change.insert? && as_insert(change)) ||
9
+ ''
10
+ end
11
+
12
+ private
13
+ def as_insert(change)
14
+ "\033[32m#{change.insert}\033[0m"
15
+ end
16
+
17
+ def as_delete(change)
18
+ "\033[31m#{change.delete}\033[0m"
19
+ end
20
+
21
+ def as_change(change)
22
+ as_delete(change) << as_insert(change)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ module Differ
2
+ module Format
3
+ module HTML
4
+ class << self
5
+ def format(change)
6
+ (change.change? && as_change(change)) ||
7
+ (change.delete? && as_delete(change)) ||
8
+ (change.insert? && as_insert(change)) ||
9
+ ''
10
+ end
11
+
12
+ private
13
+ def as_insert(change)
14
+ %Q{<ins class="differ">#{change.insert}</ins>}
15
+ end
16
+
17
+ def as_delete(change)
18
+ %Q{<del class="differ">#{change.delete}</del>}
19
+ end
20
+
21
+ def as_change(change)
22
+ as_delete(change) << as_insert(change)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ module Differ
2
+ module StringDiffer
3
+ def diff(old)
4
+ Differ.diff(self, old, $; || "\n")
5
+ end
6
+ alias :- :diff
7
+ end
8
+ end
9
+
10
+ String.class_eval do
11
+ include Differ::StringDiffer
12
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ describe Differ::Change do
4
+ before(:each) do
5
+ @format = Module.new { def self.format(c); end }
6
+ Differ.format = @format
7
+ end
8
+
9
+ describe '(empty)' do
10
+ before(:each) do
11
+ @change = Differ::Change.new()
12
+ end
13
+
14
+ it 'should have a default insert' do
15
+ @change.insert.should == ''
16
+ end
17
+
18
+ it 'should have a default delete' do
19
+ @change.delete.should == ''
20
+ end
21
+
22
+ it 'should stringify to ""' do
23
+ @format.should_receive(:format).once.and_return('')
24
+ @change.to_s.should == ''
25
+ end
26
+ end
27
+
28
+ describe '(insert only)' do
29
+ before(:each) do
30
+ @change = Differ::Change.new(:insert => 'foo')
31
+ end
32
+
33
+ it 'should populate the :insert parameter' do
34
+ @change.insert.should == 'foo'
35
+ end
36
+
37
+ it 'should have a default delete' do
38
+ @change.delete.should == ''
39
+ end
40
+
41
+ it { (@change).should be_an_insert }
42
+ end
43
+
44
+ describe '(delete only)' do
45
+ before(:each) do
46
+ @change = Differ::Change.new(:delete => 'bar')
47
+ end
48
+
49
+ it 'should have a default :insert' do
50
+ @change.insert.should == ''
51
+ end
52
+
53
+ it 'should populate the :delete parameter' do
54
+ @change.delete.should == 'bar'
55
+ end
56
+
57
+ it { (@change).should be_a_delete }
58
+ end
59
+
60
+ describe '(both insert and delete)' do
61
+ before(:each) do
62
+ @change = Differ::Change.new(:insert => 'foo', :delete => 'bar')
63
+ end
64
+
65
+ it 'should populate the :insert parameter' do
66
+ @change.insert.should == 'foo'
67
+ end
68
+
69
+ it 'should populate the :delete parameter' do
70
+ @change.delete.should == 'bar'
71
+ end
72
+
73
+ it { (@change).should be_an_insert }
74
+ it { (@change).should be_a_delete }
75
+ it { (@change).should be_a_change }
76
+ end
77
+
78
+ it "should stringify via the current format's #format method" do
79
+ @format.should_receive(:format).once
80
+ Differ::Change.new.to_s
81
+ end
82
+ end