differ 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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