# -*- coding: utf-8; mode: ruby; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- vim:fenc=utf-8:filetype=ruby:et:sw=2:ts=2:sts=2 require 'cgi' require 'git_commit_notifier/escape_helper' module GitCommitNotifier # Processes diff result # input (loaded in @diff) is an array having Hash elements: # { :action => action, :token => string } # action can be :discard_a, :discard_b or :match # output: two formatted html strings, one for the removals and one for the additions class ResultProcessor include EscapeHelper def results close_tags # close last tag [array_of_lines(@result[:removal]), array_of_lines(@result[:addition])] end # Gets length of tokenized diff in characters. # @return [FixNum] Length of the tokenized diff in characters. def length_in_chars(diff) diff.inject(0) do |length, s| token = s[:token] token_length = token.respond_to?(:jlength) ? token.jlength : token.length length + token_length end end private def initialize(diff) @diff = diff init filter_replaced_lines process end def init @result = { :addition => [], :removal => [] } @tag_open = { :addition => false, :removal => false} end def process @highlight = !@diff.select { |d| d[:action] == :match}.empty? # highlight only if block contains both matches and differences @diff.each do |diff| case diff[:action] when :match match(diff) when :discard_a discard_a(diff) when :discard_b discard_b(diff) end end end def discard_match(position, token) # replace it with 2 changes @diff[position][:action] = :discard_a @diff.insert(position, { :action => :discard_b, :token => token} ) end def filter_replaced_lines # if a block is replaced by an other one, lcs-diff will find even the single common word between the old and the new content # no need for intelligent diff in this case, simply show the removed and the added block with no highlighting # rule: if less than 33% of a block is not a match, we don't need intelligent diff for that block match_length = length_in_chars(@diff.select { |d| d[:action] == :match }) total_length = length_in_chars(@diff) if total_length.to_f / match_length > 3.3 @diff.each_with_index do |d, i| next if d[:action] != :match discard_match(i, d[:token]) end end end def match(diff) close_tags all_actions do |action| close_last_tag(diff) @result[action] << escape_content(diff[:token]) end end def discard_a(diff) open_tag(:removal, diff[:token]) close_last_tag(diff) @result[:removal] << escape_content(diff[:token]) end def discard_b(diff) open_tag(:addition, diff[:token]) close_last_tag(diff) @result[:addition] << escape_content(diff[:token]) end def all_actions [:addition, :removal].each do |action| yield(action) end end def open_tag(action, next_token) return if !@highlight || next_token.strip.empty? # don't open span tag if no highlighting is needed or the first token is empty unless @tag_open[action] klass = action == :addition ? 'aa' : 'rr' @result[action] << "" @tag_open[action] = true end end def close_tags return unless @highlight all_actions do |action| if @tag_open[action] @result[action] << "" @tag_open[action] = false end end end def close_last_tag(diff) return unless @highlight close_tags if diff[:token] == "\n" end def array_of_lines(tokens) tokens.join('').split("\n") end end end