diff-display 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/History.txt +4 -0
- data/License.txt +21 -0
- data/Manifest.txt +26 -0
- data/README.txt +58 -0
- data/Rakefile +48 -0
- data/doc/.gitignore +0 -0
- data/lib/diff-display.rb +14 -0
- data/lib/diff/display/data_structure.rb +193 -0
- data/lib/diff/display/unified.rb +15 -0
- data/lib/diff/display/unified/generator.rb +210 -0
- data/lib/diff/display/version.rb +11 -0
- data/lib/diff/renderer/base.rb +90 -0
- data/lib/diff/renderer/diff.rb +29 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +74 -0
- data/setup.rb +1585 -0
- data/test/fixtures/big.diff +590 -0
- data/test/fixtures/multiple_adds_after_rem.diff +11 -0
- data/test/fixtures/multiple_rems_then_add.diff +14 -0
- data/test/fixtures/only_add.diff +4 -0
- data/test/fixtures/only_rem.diff +4 -0
- data/test/fixtures/pseudo_recursive.diff +21 -0
- data/test/fixtures/simple.diff +12 -0
- data/test/fixtures/simple_oneliner.diff +7 -0
- data/test/fixtures/simple_rewrite.diff +8 -0
- data/test/test_api.rb +12 -0
- data/test/test_base_renderer.rb +101 -0
- data/test/test_datastructure.rb +100 -0
- data/test/test_diff_renderer.rb +14 -0
- data/test/test_generator.rb +148 -0
- data/test/test_helper.rb +25 -0
- data/test/test_unified.rb +22 -0
- metadata +123 -0
data/.gitignore
ADDED
data/History.txt
ADDED
data/License.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2008 Johan Sørensen
|
2
|
+
Copyright (c) 2003 Marcel Molina Jr.
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
a copy of this software and associated documentation files (the
|
6
|
+
"Software"), to deal in the Software without restriction, including
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest.txt
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
History.txt
|
2
|
+
License.txt
|
3
|
+
Manifest.txt
|
4
|
+
README.txt
|
5
|
+
Rakefile
|
6
|
+
config/hoe.rb
|
7
|
+
config/requirements.rb
|
8
|
+
lib/diff-display.rb
|
9
|
+
lib/diff-display/version.rb
|
10
|
+
log/debug.log
|
11
|
+
script/destroy
|
12
|
+
script/generate
|
13
|
+
script/txt2html
|
14
|
+
setup.rb
|
15
|
+
spec/diff-display_spec.rb
|
16
|
+
spec/spec.opts
|
17
|
+
spec/spec_helper.rb
|
18
|
+
tasks/deployment.rake
|
19
|
+
tasks/environment.rake
|
20
|
+
tasks/rspec.rake
|
21
|
+
tasks/website.rake
|
22
|
+
website/index.html
|
23
|
+
website/index.txt
|
24
|
+
website/javascripts/rounded_corners_lite.inc.js
|
25
|
+
website/stylesheets/screen.css
|
26
|
+
website/template.rhtml
|
data/README.txt
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
Diff::Display
|
2
|
+
=============
|
3
|
+
|
4
|
+
Diff::Display::Unified renders unified diffs into various forms. The output is
|
5
|
+
based on a callback object that's passed into the renderer
|
6
|
+
|
7
|
+
Rewrite of an (unreleased) library by Marcel Molina Jr., who wrote this it
|
8
|
+
probably back in 2004 or so.
|
9
|
+
|
10
|
+
Usage
|
11
|
+
======
|
12
|
+
|
13
|
+
irb(main):001:0> require 'diff-display'
|
14
|
+
=> true
|
15
|
+
irb(main):002:0> diff = <<EOS
|
16
|
+
irb(main):003:0" diff --git a/History.txt b/History.txt
|
17
|
+
irb(main):004:0" index 0ed7358..622c384 100644
|
18
|
+
irb(main):005:0" --- a/History.txt
|
19
|
+
irb(main):006:0" +++ b/History.txt
|
20
|
+
irb(main):007:0" @@ -1,4 +1,5 @@
|
21
|
+
irb(main):008:0" == 0.0.1 2008-01-28
|
22
|
+
irb(main):009:0"
|
23
|
+
irb(main):010:0" -* 1 major enhancement:
|
24
|
+
irb(main):011:0" - * Initial release
|
25
|
+
irb(main):012:0" +* 2 major enhancements:
|
26
|
+
irb(main):013:0" + * The Initial release
|
27
|
+
irb(main):014:0" + * stuff added
|
28
|
+
irb(main):015:0" EOS
|
29
|
+
...
|
30
|
+
irb(main):016:0> diff_display = Diff::Display::Unified.new(diff)
|
31
|
+
=> #<Diff::Display::Unified:0x331c9c @data=...
|
32
|
+
# Be boring and render it back out as a diff
|
33
|
+
irb(main):017:0> puts diff_display.render(Diff::Renderer::Diff.new)
|
34
|
+
diff --git a/History.txt b/History.txt
|
35
|
+
index 0ed7358..622c384 100644
|
36
|
+
--- a/History.txt
|
37
|
+
+++ b/History.txt
|
38
|
+
@@ -1,4 +1,5 @@
|
39
|
+
== 0.0.1 2008-01-28
|
40
|
+
|
41
|
+
-* 1 major enhancement:
|
42
|
+
- * Initial release
|
43
|
+
+* 2 major enhancements:
|
44
|
+
+ * The Initial release
|
45
|
+
+ * stuff added
|
46
|
+
|
47
|
+
See Diff::Renderer::Base for what methods your callback needs to implement
|
48
|
+
|
49
|
+
Git Repository
|
50
|
+
===============
|
51
|
+
|
52
|
+
http://gitorious.org/projects/diff-display/
|
53
|
+
|
54
|
+
|
55
|
+
License
|
56
|
+
======
|
57
|
+
|
58
|
+
Please see License.txt
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'rake/testtask'
|
5
|
+
|
6
|
+
require './lib/diff-display.rb'
|
7
|
+
|
8
|
+
task "gemspec" do
|
9
|
+
spec = Gem::Specification.new do |s|
|
10
|
+
s.name = "diff-display"
|
11
|
+
s.version = Diff::Display::VERSION::STRING
|
12
|
+
s.platform = Gem::Platform::RUBY
|
13
|
+
s.summary = "Diff::Display::Unified renders unified diffs into various forms."
|
14
|
+
|
15
|
+
s.description = %Q{Diff::Display::Unified renders unified diffs into various forms. The output is based on a callback object that's passed into the renderer}
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n") + %w(diff-display.gemspec)
|
18
|
+
s.require_path = 'lib'
|
19
|
+
s.has_rdoc = false
|
20
|
+
s.extra_rdoc_files = ['README.txt']
|
21
|
+
s.test_files = Dir['test/{test,spec}_*.rb']
|
22
|
+
|
23
|
+
s.authors = ['Johan Sørensen', 'Ketan Padegaonkar']
|
24
|
+
s.email = ['johan@johansorensen.com', 'KetanPadegaonkar@gmail.com']
|
25
|
+
s.homepage = 'http://github.com/ketan/diff-display'
|
26
|
+
|
27
|
+
s.add_development_dependency 'mocha'
|
28
|
+
end
|
29
|
+
|
30
|
+
File.open("diff-display.gemspec", "w") { |f| f << spec.to_ruby }
|
31
|
+
end
|
32
|
+
|
33
|
+
task :gem => ["gemspec"] do
|
34
|
+
rm_rf 'pkg'
|
35
|
+
sh "gem build diff-display.gemspec"
|
36
|
+
mkdir 'pkg'
|
37
|
+
mv "diff-display-#{Diff::Display::VERSION::STRING}.gem", "pkg"
|
38
|
+
end
|
39
|
+
|
40
|
+
task :test do
|
41
|
+
Rake::TestTask.new do |t|
|
42
|
+
t.libs << "test"
|
43
|
+
t.test_files = FileList['test/test_*.rb']
|
44
|
+
t.verbose = true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
task :default => :test
|
data/doc/.gitignore
ADDED
File without changes
|
data/lib/diff-display.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
module Diff
|
4
|
+
module Display
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
require "diff/display/version"
|
9
|
+
require "diff/display/data_structure"
|
10
|
+
require "diff/display/unified"
|
11
|
+
require "diff/display/unified/generator"
|
12
|
+
|
13
|
+
require "diff/renderer/base"
|
14
|
+
require "diff/renderer/diff"
|
@@ -0,0 +1,193 @@
|
|
1
|
+
module Diff
|
2
|
+
module Display
|
3
|
+
class Data < Array
|
4
|
+
def initialize
|
5
|
+
super
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_diff
|
9
|
+
diff = ""
|
10
|
+
each do |block|
|
11
|
+
block.each do |line|
|
12
|
+
line_str = line.expand_inline_changes_with(nil, nil)
|
13
|
+
case line
|
14
|
+
when HeaderLine
|
15
|
+
diff << "#{line_str}\n"
|
16
|
+
when UnModLine
|
17
|
+
diff << " #{line_str}\n"
|
18
|
+
when SepLine
|
19
|
+
diff << "\n"
|
20
|
+
when AddLine
|
21
|
+
diff << "+#{line_str}\n"
|
22
|
+
when RemLine
|
23
|
+
diff << "-#{line_str}\n"
|
24
|
+
when NonewlineLine
|
25
|
+
diff << line_str
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
diff.chomp
|
30
|
+
end
|
31
|
+
|
32
|
+
def debug
|
33
|
+
demodularize = Proc.new {|obj| obj.class.name[/\w+$/]}
|
34
|
+
each do |diff_block|
|
35
|
+
print "-" * 40, ' ', demodularize.call(diff_block)
|
36
|
+
puts
|
37
|
+
puts diff_block.map {|line|
|
38
|
+
# "%5d" % line.old_number +
|
39
|
+
"%8s" % "[#{line.old_number || '.'} #{line.new_number || '.'}]" +
|
40
|
+
" [#{demodularize.call(line)}#{'(i)' if line.inline_changes?}]" +
|
41
|
+
line
|
42
|
+
}.join("\n")
|
43
|
+
puts "-" * 40, ' '
|
44
|
+
end
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Every line from the passed in diff gets transformed into an instance of
|
50
|
+
# one of line Line class's subclasses. One subclass exists for each line
|
51
|
+
# type in a diff. As such there is an AddLine class for added lines, a RemLine
|
52
|
+
# class for removed lines, an UnModLine class for lines which remain unchanged and
|
53
|
+
# a SepLine class which represents all the lines that aren't part of the diff.
|
54
|
+
class Line < String
|
55
|
+
class << self
|
56
|
+
def add(line, line_number, inline = false)
|
57
|
+
AddLine.new(line, line_number, inline)
|
58
|
+
end
|
59
|
+
|
60
|
+
def rem(line, line_number, inline = false)
|
61
|
+
RemLine.new(line, line_number, inline)
|
62
|
+
end
|
63
|
+
|
64
|
+
def unmod(line, old_number, new_number)
|
65
|
+
UnModLine.new(line, old_number, new_number)
|
66
|
+
end
|
67
|
+
|
68
|
+
def nonewline(line)
|
69
|
+
NonewlineLine.new(line)
|
70
|
+
end
|
71
|
+
|
72
|
+
def header(line)
|
73
|
+
HeaderLine.new(line)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def initialize(line, old_number = nil, new_number = nil)
|
78
|
+
super(line)
|
79
|
+
@old_number, @new_number = old_number, new_number
|
80
|
+
@inline = false
|
81
|
+
end
|
82
|
+
attr_reader :old_number, :new_number
|
83
|
+
|
84
|
+
def identifier
|
85
|
+
self.class.name[/\w+$/].gsub(/Line$/, "").downcase.to_sym
|
86
|
+
end
|
87
|
+
|
88
|
+
def inline_changes?
|
89
|
+
# Is set in the AddLine+RemLine subclasses
|
90
|
+
@inline
|
91
|
+
end
|
92
|
+
|
93
|
+
# returns the prefix, middle and postfix parts of a line with inline changes
|
94
|
+
def segments
|
95
|
+
return self.dup unless inline_changes?
|
96
|
+
prefix, changed = self.dup.split('\\0')
|
97
|
+
changed, postfix = changed.split('\\1')
|
98
|
+
[prefix, changed, postfix]
|
99
|
+
end
|
100
|
+
|
101
|
+
# Expand any inline changes with +prefix+ and +postfix+
|
102
|
+
def expand_inline_changes_with(prefix, postfix)
|
103
|
+
return self.dup unless inline_changes?
|
104
|
+
str = self.dup
|
105
|
+
str.sub!('\\0', prefix.to_s)
|
106
|
+
str.sub!('\\1', postfix.to_s)
|
107
|
+
str
|
108
|
+
end
|
109
|
+
|
110
|
+
def inspect
|
111
|
+
%Q{#<#{self.class.name} [#{old_number.inspect}-#{new_number.inspect}] "#{self}">}
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# class AddLine < Line
|
116
|
+
# def initialize(line, line_number)
|
117
|
+
# super(line, nil, line_number)
|
118
|
+
# end
|
119
|
+
# end
|
120
|
+
#
|
121
|
+
# class RemLine < Line
|
122
|
+
# def initialize(line, line_number)
|
123
|
+
# super(line, line_number, nil)
|
124
|
+
# end
|
125
|
+
# end
|
126
|
+
class AddLine < Line
|
127
|
+
def initialize(line, line_number, inline = false)
|
128
|
+
super(line, nil, line_number)
|
129
|
+
@inline = inline
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class RemLine < Line
|
134
|
+
def initialize(line, line_number, inline = false)
|
135
|
+
super(line, line_number, nil)
|
136
|
+
@inline = inline
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class NonewlineLine < Line
|
141
|
+
def initialize(line = '\')
|
142
|
+
super(line)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class UnModLine < Line
|
147
|
+
def initialize(line, old_number, new_number)
|
148
|
+
super(line, old_number, new_number)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
class SepLine < Line
|
153
|
+
def initialize(line = '...')
|
154
|
+
super(line)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
class HeaderLine < Line
|
159
|
+
def initialize(line)
|
160
|
+
super(line)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# This class is an array which contains Line objects. Just like Line
|
165
|
+
# classes, several Block classes inherit from Block. If all the lines
|
166
|
+
# in the block are added lines then it is an AddBlock. If all lines
|
167
|
+
# in the block are removed lines then it is a RemBlock. If the lines
|
168
|
+
# in the block are all unmodified then it is an UnMod block. If the
|
169
|
+
# lines in the block are a mixture of added and removed lines then
|
170
|
+
# it is a ModBlock. There are no blocks that contain a mixture of
|
171
|
+
# modified and unmodified lines.
|
172
|
+
class Block < Array
|
173
|
+
class << self
|
174
|
+
def add; AddBlock.new end
|
175
|
+
def rem; RemBlock.new end
|
176
|
+
def mod; ModBlock.new end
|
177
|
+
def unmod; UnModBlock.new end
|
178
|
+
def header; HeaderBlock.new end
|
179
|
+
def nonewline; NonewlineBlock.new end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
#:stopdoc:#
|
184
|
+
class AddBlock < Block; end
|
185
|
+
class RemBlock < Block; end
|
186
|
+
class ModBlock < Block; end
|
187
|
+
class UnModBlock < Block; end
|
188
|
+
class SepBlock < Block; end
|
189
|
+
class HeaderBlock < Block; end
|
190
|
+
class NonewlineBlock < Block; end
|
191
|
+
#:startdoc:#
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
module Diff::Display
|
2
|
+
# Processes the diff and generates a Data object which contains the
|
3
|
+
# resulting data structure.
|
4
|
+
#
|
5
|
+
# The +run+ class method is fed a diff and returns a Data object. It will
|
6
|
+
# accept as its argument a String, an Array or a File object (or anything
|
7
|
+
# that responds to #each):
|
8
|
+
#
|
9
|
+
# Diff::Display::Unified::Generator.run(diff)
|
10
|
+
#
|
11
|
+
class Unified::Generator
|
12
|
+
|
13
|
+
# Extracts the line number info for a given diff section
|
14
|
+
LINE_NUM_RE = /^@@ [+-]([0-9]+)(?:,([0-9]+))? [+-]([0-9]+)(?:,([0-9]+))? @@/
|
15
|
+
LINE_TYPES = {'+' => :add, '-' => :rem, ' ' => :unmod, '\\' => :nonewline}
|
16
|
+
|
17
|
+
# Runs the generator on a diff and returns a Data object
|
18
|
+
def self.run(udiff)
|
19
|
+
raise ArgumentError, "Object must be enumerable" unless udiff.respond_to?(:each_line)
|
20
|
+
generator = new
|
21
|
+
udiff.each_line do |line|
|
22
|
+
begin
|
23
|
+
generator.process(line.chomp)
|
24
|
+
rescue ArgumentError => e
|
25
|
+
e.message =~ /^invalid byte sequence/ ? next : raise(e)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
generator.finish
|
29
|
+
generator.data
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
@buffer = []
|
34
|
+
@line_type = nil
|
35
|
+
@prev_line_type = nil
|
36
|
+
@offset = [0, 0]
|
37
|
+
@data = Data.new
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
# Finishes up with the generation and returns the Data object (could
|
42
|
+
# probably use a better name...maybe just #data?)
|
43
|
+
def data
|
44
|
+
@data
|
45
|
+
end
|
46
|
+
|
47
|
+
# This method is called once the generator is done with the unified
|
48
|
+
# diff. It is a finalizer of sorts. By the time it is called all data
|
49
|
+
# has been collected and processed.
|
50
|
+
def finish
|
51
|
+
# certain things could be set now that processing is done
|
52
|
+
#identify_block
|
53
|
+
end
|
54
|
+
|
55
|
+
def process(line)
|
56
|
+
if is_header_line?(line)
|
57
|
+
push Block.header
|
58
|
+
current_block << Line.header(line)
|
59
|
+
return
|
60
|
+
end
|
61
|
+
|
62
|
+
if line =~ LINE_NUM_RE
|
63
|
+
push Block.header
|
64
|
+
current_block << Line.header(line)
|
65
|
+
add_separator unless @offset[0].zero?
|
66
|
+
@line_type = nil
|
67
|
+
@offset = Array.new(2) { $3.to_i - 1 }
|
68
|
+
return
|
69
|
+
end
|
70
|
+
|
71
|
+
@line_type, line = LINE_TYPES[car(line)], cdr(line)
|
72
|
+
|
73
|
+
if @line_type == :add && @prev_line_type == :rem
|
74
|
+
@offset[0] -= 1
|
75
|
+
@buffer.push current_block.pop
|
76
|
+
@buffer.push line
|
77
|
+
process_block(:mod, false)
|
78
|
+
return
|
79
|
+
end
|
80
|
+
|
81
|
+
if LINE_TYPES.values.include?(@line_type)
|
82
|
+
@buffer.push(line.to_s)
|
83
|
+
process_block(@line_type, true)
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
def is_header_line?(line)
|
90
|
+
return true if ['+++ ', '--- '].include?(line[0,4])
|
91
|
+
return true if line =~ /^(new|delete) file mode [0-9]+$/
|
92
|
+
return true if line =~ /^diff \-\-git/
|
93
|
+
return true if line =~ /^index \w+\.\.\w+( [0-9]+)?$/i
|
94
|
+
false
|
95
|
+
end
|
96
|
+
|
97
|
+
def process_block(diff_line_type, isnew = false)
|
98
|
+
@data.pop unless isnew
|
99
|
+
push Block.send(diff_line_type)
|
100
|
+
|
101
|
+
current_line = @buffer.pop
|
102
|
+
return unless current_line
|
103
|
+
|
104
|
+
# \
|
105
|
+
if diff_line_type == :nonewline
|
106
|
+
current_block << Line.nonewline('\')
|
107
|
+
return
|
108
|
+
end
|
109
|
+
|
110
|
+
if isnew
|
111
|
+
process_line(current_line, diff_line_type)
|
112
|
+
else
|
113
|
+
process_lines_with_differences(@buffer.shift, current_line)
|
114
|
+
raise "buffer exceeded #{@buffer.inspect}" unless @buffer.empty?
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def process_line(line, type, inline = false)
|
119
|
+
case type
|
120
|
+
when :add
|
121
|
+
@offset[1] += 1
|
122
|
+
current_block << Line.send(type, line, @offset[1], inline)
|
123
|
+
when :rem
|
124
|
+
@offset[0] += 1
|
125
|
+
current_block << Line.send(type, line, @offset[0], inline)
|
126
|
+
# when :rmod
|
127
|
+
# @offset[0] += 1
|
128
|
+
# @offset[1] += 1 # TODO: is that really correct?
|
129
|
+
# current_block << Line.send(@prev_line_type, line, @offset[0])
|
130
|
+
when :unmod
|
131
|
+
@offset[0] += 1
|
132
|
+
@offset[1] += 1
|
133
|
+
current_block << Line.send(type, line, *@offset)
|
134
|
+
end
|
135
|
+
@prev_line_type = type
|
136
|
+
end
|
137
|
+
|
138
|
+
# TODO Needs a better name...it does process a line (two in fact) but
|
139
|
+
# its primary function is to add a Rem and an Add pair which
|
140
|
+
# potentially have inline changes
|
141
|
+
def process_lines_with_differences(oldline, newline)
|
142
|
+
start, ending = get_change_extent(oldline, newline)
|
143
|
+
|
144
|
+
if start.zero? && ending.zero?
|
145
|
+
process_line(oldline, :rem, false) # -
|
146
|
+
process_line(newline, :add, false) # +
|
147
|
+
else
|
148
|
+
# -
|
149
|
+
line = inline_diff(oldline, start, ending)
|
150
|
+
process_line(line, :rem, true)
|
151
|
+
# +
|
152
|
+
line = inline_diff(newline, start, ending)
|
153
|
+
process_line(line, :add, true)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Inserts string formating characters around the section of a string
|
158
|
+
# that differs internally from another line so that the Line class
|
159
|
+
# can insert the desired formating
|
160
|
+
def inline_diff(line, start, ending)
|
161
|
+
if start != 0 || ending != 0
|
162
|
+
last = ending + line.length
|
163
|
+
str = line[0...start] + '\0' + line[start...last] + '\1' + line[last...line.length]
|
164
|
+
end
|
165
|
+
str || line
|
166
|
+
end
|
167
|
+
|
168
|
+
def add_separator
|
169
|
+
push SepBlock.new
|
170
|
+
current_block << SepLine.new
|
171
|
+
end
|
172
|
+
|
173
|
+
def car(line)
|
174
|
+
line[0,1]
|
175
|
+
end
|
176
|
+
|
177
|
+
def cdr(line)
|
178
|
+
line[1..-1]
|
179
|
+
end
|
180
|
+
|
181
|
+
# Returns the current Block object
|
182
|
+
def current_block
|
183
|
+
@data.last
|
184
|
+
end
|
185
|
+
|
186
|
+
# Adds a Line object onto the current Block object
|
187
|
+
def push(line)
|
188
|
+
@data.push line
|
189
|
+
end
|
190
|
+
|
191
|
+
# Determines the extent of differences between two string. Returns
|
192
|
+
# an array containing the offset at which changes start, and then
|
193
|
+
# negative offset at which the chnages end. If the two strings have
|
194
|
+
# neither a common prefix nor a common suffic, [0, 0] is returned.
|
195
|
+
def get_change_extent(str1, str2)
|
196
|
+
start = 0
|
197
|
+
limit = [str1.size, str2.size].sort.first
|
198
|
+
while start < limit and str1[start, 1] == str2[start, 1]
|
199
|
+
start += 1
|
200
|
+
end
|
201
|
+
ending = -1
|
202
|
+
limit -= start
|
203
|
+
while -ending <= limit and str1[ending, 1] == str2[ending, 1]
|
204
|
+
ending -= 1
|
205
|
+
end
|
206
|
+
|
207
|
+
return [start, ending + 1]
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|