diff options
author | Johan Sørensen <johan@johansorensen.com> | 2008-01-11 22:53:42 +0100 |
---|---|---|
committer | Johan Sørensen <johan@johansorensen.com> | 2008-01-11 22:53:42 +0100 |
commit | d2f9f71b65c7e33a2d67a3d678824ba4ebd7794c (patch) | |
tree | 7aac726c09f3f2d559be6cf2d384f129a94013b7 /vendor/diff-display | |
parent | 2ab90d2d0bd2039e33e67c9d0e07716c9cd042d4 (diff) | |
download | gitorious-mainline-outdated-d2f9f71b65c7e33a2d67a3d678824ba4ebd7794c.zip gitorious-mainline-outdated-d2f9f71b65c7e33a2d67a3d678824ba4ebd7794c.tar.gz gitorious-mainline-outdated-d2f9f71b65c7e33a2d67a3d678824ba4ebd7794c.tar.bz2 |
Add the Diff::Display library to display diffs
Diffstat (limited to 'vendor/diff-display')
-rw-r--r-- | vendor/diff-display/LICENSE | 20 | ||||
-rw-r--r-- | vendor/diff-display/README | 1 | ||||
-rw-r--r-- | vendor/diff-display/lib/diff/display/unified.rb | 640 | ||||
-rw-r--r-- | vendor/diff-display/test/.tc_diff_display_unified.rb.swp | bin | 0 -> 12288 bytes | |||
-rw-r--r-- | vendor/diff-display/test/abstract_unit.rb | 49 | ||||
-rw-r--r-- | vendor/diff-display/test/diffs/huge_diff | 592 | ||||
-rw-r--r-- | vendor/diff-display/test/diffs/inline_changes.changes | 43 | ||||
-rw-r--r-- | vendor/diff-display/test/diffs/inline_changes.diff | 35 | ||||
-rw-r--r-- | vendor/diff-display/test/diffs/inline_changes.orig | 43 | ||||
-rw-r--r-- | vendor/diff-display/test/diffs/plain_text.changed | 7 | ||||
-rw-r--r-- | vendor/diff-display/test/diffs/plain_text.diff | 16 | ||||
-rw-r--r-- | vendor/diff-display/test/diffs/plain_text.orig | 8 | ||||
-rw-r--r-- | vendor/diff-display/test/tc_diff_display_unified.rb | 28 | ||||
-rw-r--r-- | vendor/diff-display/test/tc_parity_between_diff_and_data.rb | 28 |
14 files changed, 1510 insertions, 0 deletions
diff --git a/vendor/diff-display/LICENSE b/vendor/diff-display/LICENSE new file mode 100644 index 0000000..1828d96 --- /dev/null +++ b/vendor/diff-display/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2003 Marcel Molina Jr. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/diff-display/README b/vendor/diff-display/README new file mode 100644 index 0000000..f978725 --- /dev/null +++ b/vendor/diff-display/README @@ -0,0 +1 @@ +Marcel Molina Jr. wrote this library probably back in 2004 or so.
\ No newline at end of file diff --git a/vendor/diff-display/lib/diff/display/unified.rb b/vendor/diff-display/lib/diff/display/unified.rb new file mode 100644 index 0000000..bd5f578 --- /dev/null +++ b/vendor/diff-display/lib/diff/display/unified.rb @@ -0,0 +1,640 @@ +module Diff #:nodoc:# + module Display #:nodoc:# + # = Diff::Display::Unified + # + # Diff::Display::Unified is meant to make dealing with the presentation of + # diffs easy, customizable and succinct. It breaks a diff up into sections, + # or blocks, which are defined by the types of lines they contain. If, for + # example, there is a section where five lines have been added then an + # AddBlock is created and those five lines are placed into that AddBlock. + # The design is quite simple: The generated object is made up of Block + # objects which are themselves made up of Line objects. + # + # === Blocks + # + # Blocks represent various sections that one finds in a diff. + # There are five different Block classes: + # + # [AddBlock] + # Contains only instances of AddLine + # + # [RemBlock] + # Contains only instances of RemLine + # + # [ModBlock] + # Contains a set of RemLine objects followed by a set of AddLine + # objects + # + # [UnModBlock] + # Contains instances of UnModLine which represent sets of context lines + # that are unchanged in both the old and modified data set that + # surround Mod, Add or Rem blocks + # + # [SepBlock] + # Contains a single SepLine. SepBlocks are placed between blocks when + # the distances between one modification set and the next exceeds the + # number of context buffer surrounding them. + # + # === Lines + # + # The Line classes are much line the Block classes, just on a smaller + # scale. + # + # There are 4 lines classes: + # + # === Example + # + # Consider the following before and after on a diff. + # + # Before: + # + # - class OldName < Array + # + class NewName < Array + # + # - def initialize(boundry) + # - @boundry = boundry + # - end + # - + # def stay(the, same) + # + end + # + + # + def all_new + # + @this, @method = *IS_ALL_NEW + # + # After: + # + # ---------------------------------------- ModBlock + # 1 [RemLine] class OldName < Array + # 1 [AddLine] class NewName < Array + # ---------------------------------------- + # + # ---------------------------------------- UnModBlock + # 2 [UnModLine] + # ---------------------------------------- + # + # ---------------------------------------- RemBlock + # 3 [RemLine] def initialize(boundry) + # 4 [RemLine] @boundry = boundry + # 5 [RemLine] end + # 6 [RemLine] + # ---------------------------------------- + # + # ---------------------------------------- UnModBlock + # 7 [UnModLine] def stay(the, same) + # ---------------------------------------- + # + # ---------------------------------------- AddBlock + # 8 [AddLine] end + # 9 [AddLine] + # 10 [AddLine] def all_new + # 11 [AddLine] @this, @method = -IS_ALL_NEW + # ---------------------------------------- + # + # Note: That is just a representation of the structure of the generated + # object. Also note that this example does not include any SepBlocks since + # the changes in the example diff are all contiguous. + # + # Internally the datastructure is quite simple: The Data object has an + # array of Block objects which themselves have an array of Line + # objects. Traversing the object on the block level or line level is + # equally simple so you can focus on what to do for each type of block and + # line. + module Unified + # Every line from the passed in diff gets transformed into an instance of + # one of line Line class's subclasses. One subclass exists for each line + # type in a diff. As such there is an AddLine class for added lines, a RemLine + # class for removed lines, an UnModLine class for lines which remain unchanged and + # a SepLine class which represents all the lines that aren't part of the diff. + class Line < String + def initialize(line, line_number) + super(line) + @line_number = line_number + self + end + + def contains_inline_change? + @inline + end + + # Returns the line number of the diff line + def number + @line_number + end + + def decorate(&block) + yield self + end + + protected + + def inline_add_open; '' end + def inline_add_close; '' end + def inline_rem_open; '' end + def inline_rem_close; '' end + + def escape + self + end + + def expand + escape.gsub("\t", ' ' * tabwidth).gsub(/ ( +)|^ /) do |match| + (space + ' ') * (match.size / 2) + + space * (match.size % 2) + end + end + + def tabwidth + 4 + end + + + def space + ' ' + end + + class << self + def add(line, line_number, inline = false) + AddLine.new(line, line_number, inline) + end + + def rem(line, line_number, inline = false) + RemLine.new(line, line_number, inline) + end + + def unmod(line, line_number) + UnModLine.new(line, line_number) + end + end + end + + class AddLine < Line #:nodoc:# + def initialize(line, line_number, inline = false) + line = inline ? line % [inline_add_open, inline_add_close] : line + super(line, line_number) + @inline = inline + self + end + end + + class RemLine < Line #:nodoc:# + def initialize(line, line_number, inline = false) + line = inline ? line % [inline_rem_open, inline_rem_close] : line + super(line, line_number) + @inline = inline + self + end + end + + class UnModLine < Line #:nodoc:# + def initialize(line, line_number) + super(line, line_number) + end + end + + class SepLine < Line #:nodoc:# + def initialize(line = '...') + super(line, nil) + end + end + + # This class is an array which contains Line objects. Just like Line + # classes, several Block classes inherit from Block. If all the lines + # in the block are added lines then it is an AddBlock. If all lines + # in the block are removed lines then it is a RemBlock. If the lines + # in the block are all unmodified then it is an UnMod block. If the + # lines in the block are a mixture of added and removed lines then + # it is a ModBlock. There are no blocks that contain a mixture of + # modified and unmodified lines. + class Block < Array + def initialize + super + end + + def <<(line_object) + super(line_object) + self + end + + def decorate(&block) + yield self + end + + class << self + def add; AddBlock.new end + def rem; RemBlock.new end + def mod; ModBlock.new end + def unmod; UnModBlock.new end + end + end + + #:stopdoc:# + class AddBlock < Block; end + class RemBlock < Block; end + class ModBlock < Block; end + class UnModBlock < Block; end + class SepBlock < Block; end + #:startdoc:# + + # A Data object contains the generated diff data structure. It is an + # array of Block objects which are themselves arrays of Line objects. The + # Generator class returns a Data instance object after it is done + # processing the diff. + class Data < Array + def initialize + super + end + + def debug + demodularize = Proc.new {|obj| obj.class.name[/\w+$/]} + each do |diff_block| + print "-" * 40, ' ', demodularize.call(diff_block) + puts + puts diff_block.map {|line| + "%5d" % line.number + + " [#{demodularize.call(line)}]" + + line + }.join("\n") + puts "-" * 40, ' ' + end + end + + end + + # Processes the diff and generates a Data object which contains the + # resulting data structure. + # + # The +run+ class method is fed a diff and returns a Data object. It will + # accept as its argument a String, an Array or a File object: + # + # Diff::Display::Unified::Generator.run(diff) + # + class Generator + + # Extracts the line number info for a given diff section + LINE_NUM_RE = /@@ [+-]([0-9]+),([0-9]+) [+-]([0-9]+),([0-9]+) @@/ + LINE_TYPES = {'+' => :add, '-' => :rem, ' ' => :unmod} + + class << self + + # Runs the generator on a diff and returns a Data object without + # instantiating a Generator object + def run(udiff) + raise ArgumentError, "Object must be enumerable" unless udiff.respond_to?(:each) + generator = new + udiff.each {|line| generator.process(line.chomp)} + generator.data + end + end + + def initialize + @buffer = [] + @prev_buffer = [] + @line_type = nil + @prev_line_type = nil + @offset_base = 0 + @offset_changed = 0 + @data = Diff::Display::Unified::Data.new + self + end + + # Operates on a single line from the diff and passes along the + # collected data to the appropriate method for further processing. The + # cycle of processing is in general: + # + # process --> identify_block --> process_block --> process_line + # + def process(line) + return if ['++', '--'].include?(line[0,2]) + + if match = LINE_NUM_RE.match(line) + identify_block + add_separator unless @offset_changed.zero? + @line_type = nil + @offset_base = match[1].to_i - 1 + @offset_changed = match[3].to_i - 1 + return + end + + new_line_type, line = LINE_TYPES[car(line)], cdr(line) + + # Add line to the buffer if it's the same diff line type + # as the previous line + # + # e.g. + # + # + This is a new line + # + As is this one + # + And yet another one... + # + if new_line_type.eql?(@line_type) + @buffer.push(line) + else + # Side by side inline diff + # + # e.g. + # + # - This line just had to go + # + This line is on the way in + # + if new_line_type.eql?(LINE_TYPES['+']) and @line_type.eql?(LINE_TYPES['-']) + @prev_buffer = @buffer + @prev_line_type = @line_type + else + identify_block + end + @buffer = [line] + @line_type = new_line_type + end + end + + # Finishes up with the generation and returns the Data object (could + # probably use a better name...maybe just #data?) + def data + close + @data + end + + protected + + def identify_block + if @prev_line_type.eql?(LINE_TYPES['-']) and @line_type.eql?(LINE_TYPES['+']) + process_block(:mod, true, true) + else + if LINE_TYPES.values.include?(@line_type) + process_block(@line_type, true) + end + end + + @prev_line_type = nil + end + + def process_block(diff_line_type, new = false, old = false) + push Block.send(diff_line_type) + + # Mod block + if diff_line_type.eql?(:mod) and @prev_buffer.size & @buffer.size == 1 + process_line(@prev_buffer.first, @buffer.first) + return + end + + unroll_prev_buffer if old + unroll_buffer if new + end + + # TODO Needs a better name...it does process a line (two in fact) but + # its primary function is to add a Rem and an Add pair which + # potentially have inline changes + def process_line(oldline, newline) + start, ending = get_change_extent(oldline, newline) + + # - + line = inline_diff(oldline, start, ending) + current_block << Line.rem(line, @offset_base += 1, true) + + # + + line = inline_diff(newline, start, ending) + current_block << Line.add(line, @offset_changed += 1, true) + end + + # Inserts string formating characters around the section of a string + # that differs internally from another line so that the Line class + # can insert the desired formating + def inline_diff(line, start, ending) + line[0, start] + + '%s' + extract_change(line, start, ending) + '%s' + + line[ending, ending.abs] + end + + def add_separator + push SepBlock.new + current_block << SepLine.new + end + + def extract_change(line, start, ending) + line.size > (start - ending) ? line[start...ending] : '' + end + + def car(line) + line[0,1] + end + + def cdr(line) + line[1..-1] + end + + # Returns the current Block object + def current_block + @data.last + end + + # Adds a Line object onto the current Block object + def push(line) + @data.push line + end + + def prev_buffer + @prev_buffer + end + + def unroll_prev_buffer + return if @prev_buffer.empty? + @prev_buffer.each do |line| + @offset_base += 1 + current_block << Line.send(@prev_line_type, line, @offset_base) + end + end + + def unroll_buffer + return if @buffer.empty? + @buffer.each do |line| + @offset_changed += 1 + current_block << Line.send(@line_type, line, @offset_changed) + end + end + + # This method is called once the generator is done with the unified + # diff. It is a finalizer of sorts. By the time it is called all data + # has been collected and processed. + def close + # certain things could be set now that processing is done + identify_block + end + + # Determines the extent of differences between two string. Returns + # an array containing the offset at which changes start, and then + # negative offset at which the chnages end. If the two strings have + # neither a common prefix nor a common suffic, [0, 0] is returned. + def get_change_extent(str1, str2) + start = 0 + limit = [str1.size, str2.size].sort.first + while start < limit and str1[start, 1] == str2[start, 1] + start += 1 + end + ending = -1 + limit -= start + while -ending <= limit and str1[ending, 1] == str2[ending, 1] + ending -= 1 + end + + return [start, ending + 1] + end + end + + # The Renderer class is the single point of entry for the + # Diff::Display::Unified library. It can be used in two ways. One is to + # create a new instance which returns a Data object created by the + # Generator. This object contains the collections of Blocks and Lines + # which the user can iterate over. + # + # data_object = Diff::Display::Unified::Renderer.new(diff) + # + # The second way is to call the Renderer's +run+ class method, optionally + # passing in an instance of a class which inherits from + # Diff::Display::Unified::Callbacks. This then calls the +render+ method + # which uses the methods defined in the callback class instance to + # decorate the diff contents as it unrolls the Data object. + # + # Somewhere up above: + # + # class MyDiffCallbacks < Diff::Display::Unified::Callbacks + # + # def before_addline '<ins>' end + # def after_addline '</ins>' end + # + # end + # + # callback_obj = MyDiffCallbacks.new + # + # fully_rendered_diff = Diff::Display::Unified::Renderer.run(diff, callback_obj) + # + class Renderer + attr_reader :data + + def initialize(diff, callback_object = nil) + @callbacks = callback_object || Diff::Display::Unified::Callbacks.new + @data = Diff::Display::Unified::Generator.run(diff) + end + + # XXX The relationship between render and rendered and run is too complicated + # and nuanced + def render + @rendered = @data.inject([]) do |block_data, block| + block_data << before_method(block) + # Block must use braces rather than do/end due to precedence rules! + block_data.concat block.inject([]) { |line_data, line| + line_data << before_method(line) << line << after_method(line) + } + block_data << after_method(block) + end + end + + def rendered + (@rendered ? @rendered : render).join(new_line) + end + + class << self + def run(diff, callback_object = nil) + new(diff, callback_object).rendered + end + end + + private + + def class_name(object) + object.class.name[/\w+$/].downcase + end + + def before_method(object) + @callbacks.send('before_' + class_name(object), object) + end + + def after_method(object) + @callbacks.send('after_' + class_name(object), object) + end + + def new_line + @callbacks.new_line + end + + end + + # Defines a set of callbacks which are triggered at various stages in the + # Render class as the Data object is being unrolled. This class is meant + # to be inherited from by classes that define costume behavior by + # overriding methods to allow for the interpolation of arbitrary values + # into the diff Data object as it is being rendered. + # + # Though this seems like good functionality for a module, being able to + # define a class that inherits from this makes the interface for + # customization easier. Suggestions for improvements are much + # appreciated. + class Callbacks + + #:stopdoc:# + def before_addblock(block) '' end + def before_remblock(block) '' end + def before_modblock(block) '' end + def before_unmodblock(block) '' end + def before_sepblock(block) '' end + + def after_addblock(block) '' end + def after_remblock(block) '' end + def after_modblock(block) '' end + def after_unmodblock(block) '' end + def after_sepblock(block) '' end + + def before_addline(line) '' end + def before_remline(line) '' end + def before_modline(line) '' end + def before_unmodline(line) '' end + def before_sepline(line) '' end + + def after_addline(line) '' end + def after_remline(line) '' end + def after_modline(line) '' end + def after_unmodline(line) '' end + def after_sepline(line) '' end + + def new_line; "\n" end + #:startdoc:# + end + + #:stopdoc:# + class DebugCallbacks + + def method_missing(sym, *params) + sym.id2name + end + + end + #:startdoc:# + + # XXX This doesn't make sense anymore...How to implement a convenient way + # to redefine methods such as space and escape? + # Mostly a convenience class at this point that just overwrites various + # customization methods + class HTMLGenerator < Generator #:nodoc:# + + # This and the space method now don't work/make sense now that those + # methods are part of the Line class and there certainly won't be an + # HTMLLine class + def escape(text) + text.gsub('&', '&'). + gsub('<', '<' ). + gsub('>', '>' ). + gsub('"', '"') + end + + def space + ' ' + end + + end + + # How to implement this? See doc string for HTMLGenerator + class ASCIIGenerator < Generator #:nodoc:# + end + + end + end +end diff --git a/vendor/diff-display/test/.tc_diff_display_unified.rb.swp b/vendor/diff-display/test/.tc_diff_display_unified.rb.swp Binary files differnew file mode 100644 index 0000000..28b76c5 --- /dev/null +++ b/vendor/diff-display/test/.tc_diff_display_unified.rb.swp diff --git a/vendor/diff-display/test/abstract_unit.rb b/vendor/diff-display/test/abstract_unit.rb new file mode 100644 index 0000000..2926bf3 --- /dev/null +++ b/vendor/diff-display/test/abstract_unit.rb @@ -0,0 +1,49 @@ +$:.unshift(File.dirname(__FILE__) + '/../lib') +require 'diff/display/unified' + +module Kernel + def load_diffs(*diffs) + loaded_diffs = Hash.new({}) + diff_path = 'diffs/' + diffs.each do |diff_name| + diff = IO.readlines(diff_path + diff_name.id2name + '.diff') + data = Diff::Display::Unified::Generator.run(diff) + loaded_diffs[diff_name] = {:diff => diff, :data => data} + end + loaded_diffs + end + alias_method :load_diff, :load_diffs + + def load_all_diffs(path = 'diffs') + load_diffs *Dir.glob(path + '/*.diff').map {|d| File.basename(d, '.*').intern} + end + +end + +class Array + def normalize_diff + diff = delete_if {|elem| %w{++ -- @@}.include?(elem[0,2])} + diff.map {|elem| elem.gsub(/^./, '').chomp} + end + + def normalize_diff_object + delete_if {|elem| elem.is_a? Diff::Display::Unified::SepBlock}.flatten + end + + def diff_inline_line_numbers + numbers = [] + diff = delete_if {|elem| %w{++ -- @@}.include?(elem[0,2])} + diff.each_with_index do |elem, idx| + numbers.concat([idx - 1, idx]) if self[idx][0,1].eql?('+') and self[idx - 1][0,1].eql?('-') + end + numbers + end + + def data_inline_line_numbers + numbers = [] + normalize_diff_object.each_with_index do |elem, idx| + numbers.push idx if elem.contains_inline_change? + end + numbers + end +end diff --git a/vendor/diff-display/test/diffs/huge_diff b/vendor/diff-display/test/diffs/huge_diff new file mode 100644 index 0000000..b1918e4 --- /dev/null +++ b/vendor/diff-display/test/diffs/huge_diff @@ -0,0 +1,592 @@ +Index: unified.rb +=================================================================== +--- unified.rb (revision 620) ++++ unified.rb (revision 644) +@@ -1,298 +1,390 @@ + module Diff + module Display + module Unified +- +- LINE_RE = /@@ [+-]([0-9]+),([0-9]+) [+-]([0-9]+),([0-9]+) @@/ +- TABWIDTH = 4 +- SPACE = ' ' #' ' +- # By defaul don't wrap inline diffs in anything +- INLINE_REM_OPEN = "\e[4;33m" +- INLINE_REM_CLOSE = "\e[m" +- INLINE_ADD_OPEN = "\e[4;35m" +- INLINE_ADD_CLOSE = "\e[m" +- ESCAPE_HTML = false +- + class Line < String +- attr_reader :add_lineno, :rem_lineno +- def initialize(line, type, add_lineno, rem_lineno = add_lineno) ++ def initialize(line, line_number) + super(line) +- @type = type +- @add_lineno = add_lineno +- @rem_lineno = rem_lineno ++ @line_number = line_number ++ self + end + ++ def contains_inline_change? ++ @inline ++ end ++ + def number +- add_lineno ? add_lineno : rem_lineno ++ @line_number + end + +- def type +- @type ++ def decorate(&block) ++ yield self + end + +- class << self +- def add(line, add_lineno) +- AddLine.new(line, add_lineno) ++ def inline_add_open; '' end ++ def inline_add_close; '' end ++ def inline_rem_open; '' end ++ def inline_rem_close; '' end ++ ++ protected ++ ++ def escape ++ self + end + +- def rem(line, rem_lineno) +- RemLine.new(line, rem_lineno) ++ def expand ++ escape.gsub("\t", ' ' * tabwidth).gsub(/ ( +)|^ /) do |match| ++ (space + ' ') * (match.size / 2) + ++ space * (match.size % 2) ++ end + end + +- def unmod(line, lineno) +- UnModLine.new(line, lineno) ++ def tabwidth ++ 4 + end + +- def mod(line, lineno) +- ModLine.new(line, lineno) ++ ++ def space ++ ' ' + end ++ ++ class << self ++ def add(line, line_number, inline = false) ++ AddLine.new(line, line_number, inline) ++ end ++ ++ def rem(line, line_number, inline = false) ++ RemLine.new(line, line_number, inline) ++ end ++ ++ def unmod(line, line_number) ++ UnModLine.new(line, line_number) ++ end + end + end + + class AddLine < Line +- def initialize(line, add_lineno) +- super(line, 'add', add_lineno, nil) ++ def initialize(line, line_number, inline = false) ++ line = inline ? line % [inline_add_open, inline_add_close] : line ++ super(line, line_number) ++ @inline = inline ++ self + end + end + + class RemLine < Line +- def initialize(line, rem_lineno) +- super(line, 'rem', nil, rem_lineno) ++ def initialize(line, line_number, inline = false) ++ line = inline ? line % [inline_rem_open, inline_rem_close] : line ++ super(line, line_number) ++ @inline = inline ++ self + end + end + + class UnModLine < Line +- def initialize(line, lineno) +- super(line, 'unmod', lineno) ++ def initialize(line, line_number) ++ super(line, line_number) + end + end + +- class ModLine < Line +- def initialize(line, lineno) +- super(line, 'mod', lineno) ++ class SepLine < Line ++ def initialize(line = '...') ++ super(line, nil) + end + end + ++ # This class is an array which contains Line objects. Just like Line ++ # classes, several Block classes inherit from Block. If all the lines ++ # in the block are added lines then it is an AddBlock. If all lines ++ # in the block are removed lines then it is a RemBlock. If the lines ++ # in the block are all unmodified then it is an UnMod block. If the ++ # lines in the block are a mixture of added and removed lines then ++ # it is a ModBlock. There are no blocks that contain a mixture of ++ # modified and unmodified lines. + class Block < Array +- def initialize(type) +- super(0) +- @type = type ++ def initialize ++ super ++ @line_types = [] + end + + def <<(line_object) + super(line_object) +- (@line_types ||= []).push(line_object.type) +- @line_types.uniq! ++ line_class = line_object.class.name[/\w+$/] ++ @line_types.push(line_class) unless @line_types.include?(line_class) + self + end + ++ def decorate(&block) ++ yield self ++ end ++ + def line_types + @line_types + end + +- def type +- @type ++ class << self ++ def add; AddBlock.new end ++ def rem; RemBlock.new end ++ def mod; ModBlock.new end ++ def unmod; UnModBlock.new end + end + end + +- class Generator < Array ++ class AddBlock < Block; end ++ class RemBlock < Block; end ++ class ModBlock < Block; end ++ class UnModBlock < Block; end ++ class SepBlock < Block; end + ++ # This data object contains the generated diff data structure. It is an ++ # array of Block objects which are themselves arrays of Line objects. The ++ # Generator class returns a Data instance object after it is done ++ # processing the diff. ++ class Data < Array ++ def initialize ++ super ++ end ++ ++ def debug ++ demodularize = Proc.new {|obj| obj.class.name[/\w+$/]} ++ each do |diff_block| ++ print "*" * 40, ' ', demodularize.call(diff_block) ++ puts ++ puts diff_block.map {|line| ++ "%5d" % line.number + ++ " [#{demodularize.call(line)}]" + ++ line ++ }.join("\n") ++ puts "*" * 40, ' ' ++ end ++ end ++ ++ end ++ ++ # Processes the diff and generates a Data object which contains the ++ # resulting data structure. ++ class Generator ++ ++ # Extracts the line number info for a given diff section ++ LINE_NUM_RE = /@@ [+-]([0-9]+),([0-9]+) [+-]([0-9]+),([0-9]+) @@/ ++ LINE_TYPES = {'+' => :add, '-' => :rem, ' ' => :unmod} ++ + class << self +- def run(udiff, options = {}) +- generator = new(options) +- udiff.split("\n").each {|line| generator.build(line) } +- generator.close +- generator ++ ++ # Runs the generator on a diff and returns a Data object without ++ # instantiating a Generator object ++ def run(udiff) ++ raise ArgumentError, "Object must be enumerable" unless udiff.respond_to?(:each) ++ generator = new ++ udiff.each {|line| generator.process(line.chomp)} ++ generator.render + end + end + +- def initialize(options = {}) +- super(0) +- default_options = {:inline_add_open => INLINE_ADD_OPEN, +- :inline_add_close => INLINE_ADD_CLOSE, +- :inline_rem_open => INLINE_REM_OPEN, +- :inline_rem_close => INLINE_REM_CLOSE, +- :escape_html => ESCAPE_HTML, +- :tabwidth => TABWIDTH, +- :space => SPACE} +- +- @options = default_options.merge(options) +- @block = [] +- @ttype = nil +- @p_block = [] +- @p_type = nil +- @changeno = -1 +- @blockno = 0 ++ def initialize ++ @buffer = [] ++ @prev_buffer = [] ++ @line_type = nil ++ @prev_line_type = nil + @offset_base = 0 + @offset_changed = 0 ++ @data = Diff::Display::Unified::Data.new ++ self + end + +- def current_block +- last ++ # Operates on a single line from the diff and passes along the ++ # collected data to the appropriate method for further processing. The ++ # cycle of processing is in general: ++ # ++ # process --> identify_block --> process_block --> process_line ++ # ++ def process(line) ++ return if ['++', '--'].include?(line[0,2]) ++ ++ if match = LINE_NUM_RE.match(line) ++ identify_block ++ push SepBlock.new and current_block << SepLine.new unless @offset_changed.zero? ++ @line_type = nil ++ @offset_base = match[1].to_i - 1 ++ @offset_changed = match[3].to_i - 1 ++ return ++ end ++ ++ new_line_type, line = LINE_TYPES[car(line)], cdr(line) ++ ++ # Add line to the buffer if it's the same diff line type ++ # as the previous line ++ # ++ # e.g. ++ # ++ # + This is a new line ++ # + As is this one ++ # + And yet another one... ++ # ++ if new_line_type.eql?(@line_type) ++ @buffer.push(line) ++ else ++ # Side by side inline diff ++ # ++ # e.g. ++ # ++ # - This line just had to go ++ # + This line is on the way in ++ # ++ if new_line_type.eql?(LINE_TYPES['+']) and @line_type.eql?(LINE_TYPES['-']) ++ @prev_buffer = @buffer ++ @prev_line_type = @line_type ++ else ++ identify_block ++ end ++ @buffer = [line] ++ @line_type = new_line_type ++ end + end + ++ # Finishes up with the generation and returns the Data object (could ++ # probably use a better name...maybe just #data?) + def render + close +- self ++ @data + end +- +- def escape(text) +- return '' unless text +- return text unless @options[:escape_html] +- text.gsub('&', '&'). +- gsub('<', '<' ). +- gsub('>', '>' ). +- gsub('"', '"') +- end + +- def expand(text) +- escape(text).gsub(/ ( +)|^ /) do |match| +- (@options[:space] + ' ') * (match.size / 2) + +- @options[:space] * (match.size % 2) +- end +- end ++ protected + +- def inline_diff(line, start, ending, change) +- expand(line[0, start]) + +- change + +- expand(line[ending, ending.abs]) +- end ++ def identify_block ++ if @prev_line_type.eql?(LINE_TYPES['-']) and @line_type.eql?(LINE_TYPES['+']) ++ process_block(:mod, {:old => @prev_buffer, :new => @buffer}) ++ else ++ if LINE_TYPES.values.include?(@line_type) ++ process_block(@line_type, {:new => @buffer}) ++ end ++ end + +- def write_line(oldline, newline) +- start, ending = get_change_extent(oldline, newline) +- change = '' +- if oldline.size > start - ending +- change = @options[:inline_rem_open] + +- expand(oldline[start...ending]) + +- @options[:inline_rem_close] ++ @prev_line_type = nil + end + +- line = inline_diff(oldline, start, ending, change) +- current_block << Line.rem(line, @offset_base) ++ def process_block(diff_line_type, blocks = {:old => nil, :new => nil}) ++ push Block.send(diff_line_type) ++ old, new = blocks[:old], blocks[:new] + +- change = '' +- if newline.size > start - ending +- change = @options[:inline_add_open] + +- expand(newline[start...ending]) + +- @options[:inline_add_close] ++ # Mod block ++ if diff_line_type.eql?(:mod) and old.size & new.size == 1 ++ process_line(old.first, new.first) ++ return ++ end ++ ++ if old and not old.empty? ++ old.each do |line| ++ @offset_base += 1 ++ current_block << Line.send(@prev_line_type, line, @offset_base) ++ end ++ end ++ ++ if new and not new.empty? ++ new.each do |line| ++ @offset_changed += 1 ++ current_block << Line.send(@line_type, line, @offset_changed) ++ end ++ end + end + +- line = inline_diff(newline, start, ending, change) +- current_block << Line.add(line, @offset_changed) +- end ++ # TODO Needs a better name...it does process a line (two in fact) but ++ # its primary function is to add a Rem and an Add pair which ++ # potentially have inline changes ++ def process_line(oldline, newline) ++ start, ending = get_change_extent(oldline, newline) + +- def write_block(dtype, old = nil, new = nil) +- push Block.new(dtype) ++ # - ++ line = inline_diff(oldline, start, ending) ++ current_block << Line.rem(line, @offset_base += 1, true) + +- if dtype == 'mod' and old.size == 1 and new.size == 1 +- write_line(old.first, new.first) +- return ++ # + ++ line = inline_diff(newline, start, ending) ++ current_block << Line.add(line, @offset_changed += 1, true) + end + +- if old and not old.empty? +- old.each do |e| +- current_block << Line.send(dtype, expand(e), @offset_base) +- @offset_base += 1 +- end ++ # Inserts string formating characters around the section of a string ++ # that differs internally from another line so that the Line class ++ # can insert the desired formating ++ def inline_diff(line, start, ending) ++ line[0, start] + ++ '%s' + extract_change(line, start, ending) + '%s' + ++ line[ending, ending.abs] + end + +- if new and not new.empty? +- new.each do |e| +- current_block << Line.send(dtype, expand(e), @offset_changed) +- @offset_changed += 1 +- end ++ def extract_change(line, start, ending) ++ line.size > (start - ending) ? line[start...ending] : '' + end +- end + +- def print_block +- if @p_type.eql?('-') and @ttype.eql?('+') +- write_block('mod', @p_block, @block) +- else +- case @ttype +- when '+' +- write_block('add', @block) +- when '-' +- write_block('rem', @block) +- when ' ' +- write_block('unmod', @block) +- end ++ def car(line) ++ line[0,1] + end + +- @block = @p_block = [] +- @p_type = ' ' +- @blockno += 1 +- end ++ def cdr(line) ++ line[1..-1] ++ end + +- def build(text) +- # TODO Names of the files and their versions go here perhaps ++ # Returns the current Block object ++ def current_block ++ @data.last ++ end + +- return if ['++', '--'].include?(text[0,2]) ++ # Adds a Line object onto the current Block object ++ def push(line) ++ @data.push line ++ end + +- if match = LINE_RE.match(text) +- print_block +- @changeno += 1 +- @blockno = 0 +- @offset_base = match[1].to_i - 1 +- @offset_changed = match[3].to_i - 1 +- return ++ # This method is called once the generator is done with the unified ++ # diff. It is a finalizer of sorts. By the time it is called all data ++ # has been collected and processed. ++ def close ++ # certain things could be set now that processing is done ++ identify_block + end + +- # Set ttype to first character of line +- ttype = text[0, 1] +- text = text[1..-1] +- text = text.gsub("\t", ' ' * @options[:tabwidth]) if text +- # If it's the same type of mod as the last line push this line onto the +- # block stack +- if ttype.eql?(@ttype) +- @block.push(text) +- else +- # If we have a side by side subtraction/addition +- if ttype == '+' and @ttype == '-' +- @p_block = @block +- @p_type = @ttype +- else +- print_block ++ # Determines the extent of differences between two string. Returns ++ # an array containing the offset at which changes start, and then ++ # negative offset at which the chnages end. If the two strings have ++ # neither a common prefix nor a common suffic, [0, 0] is returned. ++ def get_change_extent(str1, str2) ++ start = 0 ++ limit = [str1.size, str2.size].sort.first ++ while start < limit and str1[start, 1] == str2[start, 1] ++ start += 1 + end +- @block = [text] +- @ttype = ttype ++ ending = -1 ++ limit -= start ++ while -ending <= limit and str1[ending, 1] == str2[ending, 1] ++ ending -= 1 ++ end ++ ++ return [start, ending + 1] + end +- end ++ end + +- def debug +- each do |diff_block| +- print "*" * (40 - diff_block.type.size / 2), ' ', diff_block.type +- puts +- puts diff_block.map {|line| "#{line.number}" << line << " [#{line.type}]"}.join("\n") +- print "Line types:" +- puts diff_block.line_types.join(", ") +- puts +- end ++ # Mostly a convinience class at this point that just overwrites various ++ # customization methods ++ class HTMLGenerator < Generator ++ ++ # This and the space method now don't work/make sense now that those ++ # methods are part of the Line class and there certainly won't be an ++ # HTMLLine class ++ def escape(text) ++ text.gsub('&', '&'). ++ gsub('<', '<' ). ++ gsub('>', '>' ). ++ gsub('"', '"') + end + +- def close +- # certain things could be set now that processing is done +- print_block ++ def space ++ ' ' + end + +- # Determines the extent of differences between two string. Returns +- # an array containing the offset at which changes start, and then +- # negative offset at which the chnages end. If the two strings have +- # neither a common prefix nor a common suffic, [0, 0] is returned. +- def get_change_extent(str1, str2) +- start = 0 +- limit = [str1.size, str2.size].sort.first +- while start < limit and str1[start, 1] == str2[start, 1] +- start += 1 +- end +- ending = -1 +- limit -= start +- while -ending <= limit and str1[ending, 1] == str2[ending, 1] +- ending -= 1 +- end ++ end + +- return [start, ending + 1] +- end ++ # See doc string for HTMLGenerator ++ class ASCIIGenerator < Generator + end ++ + end + end + end +- diff --git a/vendor/diff-display/test/diffs/inline_changes.changes b/vendor/diff-display/test/diffs/inline_changes.changes new file mode 100644 index 0000000..a0b50ab --- /dev/null +++ b/vendor/diff-display/test/diffs/inline_changes.changes @@ -0,0 +1,43 @@ +Tom Copeland is the administrator for RubyForge, which opened in July 2003 +and now hosts over 450 projects. Tom works at Rich Kilmer's red-minded +company InfoEther. + +1. So, give us an idea. How much time do you put into RubyForge? + +Hm. Maybe... ninteen hours a day of actual work. So I mull over it +constantly. And occasionally things arise that take more time, like +when we got the new hardware, and putting out the Wiki spam fires, +and so forth. + +2. RubyForge has been an incredible contribution to Ruby coders. You are +so responsive and it's great to have an admin who is directly involved +with Ruby-Talk and all the various projects. What drives you to work on +RubyForge? TELL US!! WHY ARE YOU SO BLASTED GOOD?? + +Thank you! :blushes: + +I don't know...it's fun to work on RubyForge, because I see lots of new +projects. For example, seeing Michael Neumann's postgres-pr project come +out was neat, because I was able to use it on RubyForge right away. Also, +I get to chat with lots of Rubyists about various things, and most of the +folks I talk with are much more savvy than I am, so I'm always learning +lots about Ruby and system administration and what-have-you. Especially +the what-have-you. + +3. You've added so many excellent components: project wikis, RubyGems +integration, BitTorrents. Are there any components of RubyForge that +are bothersome? Anything you regret? + +Let's not dwell on the sad things...we must leave them in our +wake. Instead, let us look ahead to a bright future, with Ruwiki replacing +UseMod as the default Wiki, with Subversion support, and with RubyForge +sitting on a 32 CPU Sun E10K behind a T3 pipe all purchased by my lottery +winnings! Note: some future items may be more likely than others. + +4. Will you find a random image on Google to share with us? + +Love those McGuffey Readers: + +5. Getting anything cool for Christmas? + +A 12 passenger bicycle to carry my ever-growing family to and fro. diff --git a/vendor/diff-display/test/diffs/inline_changes.diff b/vendor/diff-display/test/diffs/inline_changes.diff new file mode 100644 index 0000000..2a66106 --- /dev/null +++ b/vendor/diff-display/test/diffs/inline_changes.diff @@ -0,0 +1,35 @@ +--- inline_changes.orig 2004-12-24 08:37:18.000000000 +0000 ++++ inline_changes.changes 2004-12-24 10:19:11.000000000 +0000 +@@ -4,7 +4,7 @@ + + 1. So, give us an idea. How much time do you put into RubyForge? + +-Hm. Maybe... half an hour a day of actual work. But I mull over it ++Hm. Maybe... ninteen hours a day of actual work. So I mull over it + constantly. And occasionally things arise that take more time, like + when we got the new hardware, and putting out the Wiki spam fires, + and so forth. +@@ -16,7 +16,7 @@ + + Thank you! :blushes: + +-I don't know... it's fun to work on RubyForge, because I see lots of new ++I don't know...it's fun to work on RubyForge, because I see lots of new + projects. For example, seeing Michael Neumann's postgres-pr project come + out was neat, because I was able to use it on RubyForge right away. Also, + I get to chat with lots of Rubyists about various things, and most of the +@@ -28,7 +28,7 @@ + integration, BitTorrents. Are there any components of RubyForge that + are bothersome? Anything you regret? + +-Let's not dwell on the sad things... we must leave them in our ++Let's not dwell on the sad things...we must leave them in our + wake. Instead, let us look ahead to a bright future, with Ruwiki replacing + UseMod as the default Wiki, with Subversion support, and with RubyForge + sitting on a 32 CPU Sun E10K behind a T3 pipe all purchased by my lottery +@@ -40,4 +40,4 @@ + + 5. Getting anything cool for Christmas? + +-A 12 passenger van to contain carry my ever-growing family to and fro. ++A 12 passenger bicycle to carry my ever-growing family to and fro. diff --git a/vendor/diff-display/test/diffs/inline_changes.orig b/vendor/diff-display/test/diffs/inline_changes.orig new file mode 100644 index 0000000..2985019 --- /dev/null +++ b/vendor/diff-display/test/diffs/inline_changes.orig @@ -0,0 +1,43 @@ +Tom Copeland is the administrator for RubyForge, which opened in July 2003 +and now hosts over 450 projects. Tom works at Rich Kilmer's red-minded +company InfoEther. + +1. So, give us an idea. How much time do you put into RubyForge? + +Hm. Maybe... half an hour a day of actual work. But I mull over it +constantly. And occasionally things arise that take more time, like +when we got the new hardware, and putting out the Wiki spam fires, +and so forth. + +2. RubyForge has been an incredible contribution to Ruby coders. You are +so responsive and it's great to have an admin who is directly involved +with Ruby-Talk and all the various projects. What drives you to work on +RubyForge? TELL US!! WHY ARE YOU SO BLASTED GOOD?? + +Thank you! :blushes: + +I don't know... it's fun to work on RubyForge, because I see lots of new +projects. For example, seeing Michael Neumann's postgres-pr project come +out was neat, because I was able to use it on RubyForge right away. Also, +I get to chat with lots of Rubyists about various things, and most of the +folks I talk with are much more savvy than I am, so I'm always learning +lots about Ruby and system administration and what-have-you. Especially +the what-have-you. + +3. You've added so many excellent components: project wikis, RubyGems +integration, BitTorrents. Are there any components of RubyForge that +are bothersome? Anything you regret? + +Let's not dwell on the sad things... we must leave them in our +wake. Instead, let us look ahead to a bright future, with Ruwiki replacing +UseMod as the default Wiki, with Subversion support, and with RubyForge +sitting on a 32 CPU Sun E10K behind a T3 pipe all purchased by my lottery +winnings! Note: some future items may be more likely than others. + +4. Will you find a random image on Google to share with us? + +Love those McGuffey Readers: + +5. Getting anything cool for Christmas? + +A 12 passenger van to contain carry my ever-growing family to and fro. diff --git a/vendor/diff-display/test/diffs/plain_text.changed b/vendor/diff-display/test/diffs/plain_text.changed new file mode 100644 index 0000000..175aa26 --- /dev/null +++ b/vendor/diff-display/test/diffs/plain_text.changed @@ -0,0 +1,7 @@ +Following a recent article which corrolated a language's failure with +the facial hair of its designer, Larry Wall offered this dishearting pic of his +naked-as-a-baby's butt cheeks. If your resolution is too high to make out the shot, +then you're missing out on five metric tons of Awesome. + +You also might note that Wall's blog is all tricked out with Christmas +trees and stuff. diff --git a/vendor/diff-display/test/diffs/plain_text.diff b/vendor/diff-display/test/diffs/plain_text.diff new file mode 100644 index 0000000..aa2c6de --- /dev/null +++ b/vendor/diff-display/test/diffs/plain_text.diff @@ -0,0 +1,16 @@ +--- plain_text.orig 2004-12-24 08:36:11.000000000 +0000 ++++ plain_text.changed 2004-12-24 08:45:53.000000000 +0000 +@@ -1,8 +1,7 @@ +-Following a recent article which corrolated a language's success with +-the facial hair of its designer, Matz offered this spritely pic of his +-Christmas beard. If your resolution is too high to make out the shot, ++Following a recent article which corrolated a language's failure with ++the facial hair of its designer, Larry Wall offered this dishearting pic of his ++naked-as-a-baby's butt cheeks. If your resolution is too high to make out the shot, + then you're missing out on five metric tons of Awesome. + +-You also might note that Matz' blog is all tricked out with Christmas +-trees and stuff. See, the smartest man alive is still capable of +-appreciating little animated GIFs. ++You also might note that Wall's blog is all tricked out with Christmas ++trees and stuff. diff --git a/vendor/diff-display/test/diffs/plain_text.orig b/vendor/diff-display/test/diffs/plain_text.orig new file mode 100644 index 0000000..fbb0e58 --- /dev/null +++ b/vendor/diff-display/test/diffs/plain_text.orig @@ -0,0 +1,8 @@ +Following a recent article which corrolated a language's success with +the facial hair of its designer, Matz offered this spritely pic of his +Christmas beard. If your resolution is too high to make out the shot, +then you're missing out on five metric tons of Awesome. + +You also might note that Matz' blog is all tricked out with Christmas +trees and stuff. See, the smartest man alive is still capable of +appreciating little animated GIFs. diff --git a/vendor/diff-display/test/tc_diff_display_unified.rb b/vendor/diff-display/test/tc_diff_display_unified.rb new file mode 100644 index 0000000..8758d5a --- /dev/null +++ b/vendor/diff-display/test/tc_diff_display_unified.rb @@ -0,0 +1,28 @@ +require 'abstract_unit' +require 'test/unit' +require 'yaml' + +class TestDiffDisplay < Test::Unit::TestCase + + def setup + @diffs = load_all_diffs + @diffs_with_inline_changes = load_diffs :inline_changes + end + + def test_parity_of_diffs_and_data_objects + @diffs.keys.each do |d| + assert_equal(@diffs[d][:data].normalize_diff_object, + @diffs[d][:diff].normalize_diff, + "Data object and diff file for #{d} don't match") + end + end + + def test_parity_of_inline_changes + @diffs_with_inline_changes.keys.each do |d| + assert_equal(@diffs[d][:data].data_inline_line_numbers, + @diffs[d][:diff].diff_inline_line_numbers, + "Inline change line numbers don't match up for #{d}") + end + end + +end diff --git a/vendor/diff-display/test/tc_parity_between_diff_and_data.rb b/vendor/diff-display/test/tc_parity_between_diff_and_data.rb new file mode 100644 index 0000000..8758d5a --- /dev/null +++ b/vendor/diff-display/test/tc_parity_between_diff_and_data.rb @@ -0,0 +1,28 @@ +require 'abstract_unit' +require 'test/unit' +require 'yaml' + +class TestDiffDisplay < Test::Unit::TestCase + + def setup + @diffs = load_all_diffs + @diffs_with_inline_changes = load_diffs :inline_changes + end + + def test_parity_of_diffs_and_data_objects + @diffs.keys.each do |d| + assert_equal(@diffs[d][:data].normalize_diff_object, + @diffs[d][:diff].normalize_diff, + "Data object and diff file for #{d} don't match") + end + end + + def test_parity_of_inline_changes + @diffs_with_inline_changes.keys.each do |d| + assert_equal(@diffs[d][:data].data_inline_line_numbers, + @diffs[d][:diff].diff_inline_line_numbers, + "Inline change line numbers don't match up for #{d}") + end + end + +end |