class Rcov::TextCoverageDiff

:nodoc:

def SERIALIZER

def SERIALIZER
  # mfp> this was going to be YAML but I caught it failing at basic
  # round-tripping, turning "\n" into "" and corrupting the data, so
  # it must be Marshal for now
  Marshal
end

def compare_state

def compare_state
  return unless verify_diff_available
  begin
    format, prev_state = File.open(@state_file){|f| self.SERIALIZER.load(f) }
  rescue
    $stderr.puts <<-EOF
    Couldn't load coverage data from #{@state_file}.
    EOF
    return # '
  end
  if !(Array === format) or
    FORMAT_VERSION[0] != format[0] || FORMAT_VERSION[1] < format[1]
    $stderr.puts <<-EOF
    Couldn't load coverage data from #{@state_file}.
    The file is saved in the format  #{format.inspect[0..20]}.
    This rcov executable understands #{FORMAT_VERSION.inspect}.
    EOF
    return # '
  end
  each_file_pair_sorted do |filename, fileinfo|
    old_data = Tempfile.new("#{mangle_filename(filename)}-old")
    new_data = Tempfile.new("#{mangle_filename(filename)}-new")
    if prev_state.has_key? filename
      old_code, old_cov = prev_state[filename].values_at(:lines, :coverage)
      old_code.each_with_index do |line, i|
        prefix = old_cov[i] ? "   " : "!! "
        old_data.write "#{prefix}#{line}"
      end
    else
      old_data.write ""
    end
    old_data.close
    SCRIPT_LINES__[filename].each_with_index do |line, i|
      prefix = fileinfo.coverage[i] ? "   " : "!! "
      new_data.write "#{prefix}#{line}"
    end
    new_data.close
    diff = `#{@diff_cmd} -u "#{old_data.path}" "#{new_data.path}"`
    new_uncovered_hunks = process_unified_diff(filename, diff)
    old_data.close!
    new_data.close!
    display_hunks(filename, new_uncovered_hunks)
  end
end

def display_hunks(filename, hunks)

def display_hunks(filename, hunks)
  return if hunks.empty?
  puts
  puts "=" * 80
  puts "!!!!! Uncovered code introduced in #{filename}"
  hunks.each do |offset, lines|
    if @gcc_output
      lines.each_with_index do |line,i|
        lineno = offset + i
        flag = (/^!! / !~ line) ? "-" : ":"
        prefix = "#{filename}#{flag}#{lineno}#{flag}"
        puts "#{prefix}#{line[3..-1]}"
      end
    elsif @color
      puts "### #{filename}:#{offset}"
      lines.each do |line|
        prefix = (/^!! / !~ line) ? "\e[32;40m" : "\e[31;40m"
        puts "#{prefix}#{line[3..-1].chomp}\e[37;40m"
      end
    else
      puts "### #{filename}:#{offset}"
      puts lines
    end
  end
end

def execute

def execute
  case @mode
  when :record
    record_state
  when :compare
    compare_state
  else
    raise "Unknown TextCoverageDiff mode: #{mode.inspect}."
  end
end

def initialize(opts = {})

def initialize(opts = {})
  options = DEFAULT_OPTS.clone.update(opts)
  @textmode = options[:textmode]
  @color = options[:color]
  @mode = options[:coverage_diff_mode]
  @state_file = options[:coverage_diff_file]
  @diff_cmd = options[:diff_cmd]
  @gcc_output = options[:gcc_output]
  super(options)
end

def process_unified_diff(filename, diff)

def process_unified_diff(filename, diff)
  current_hunk = []
  current_hunk_start = 0
  keep_current_hunk = false
  state = :init
  interesting_hunks = []
  diff.each_with_index do |line, i|
    #puts "#{state} %5d #{line}" % i
    case state
    when :init
      if md = HUNK_HEADER.match(line)
        current_hunk = []
        current_hunk_start = md[1].to_i
        state = :body
      end
    when :body
      case line
      when HUNK_HEADER
        new_start = $1.to_i
        if keep_current_hunk
          interesting_hunks << [current_hunk_start, current_hunk]
        end
        current_hunk_start = new_start
        current_hunk = []
        keep_current_hunk = false
      when /^-/
        # ignore
      when /^\+!! /
        keep_current_hunk = true
        current_hunk << line[1..-1]
      else
        current_hunk << line[1..-1]
      end
    end
  end
  if keep_current_hunk
    interesting_hunks << [current_hunk_start, current_hunk]
  end
  interesting_hunks
end

def record_state

def record_state
  state = {}
  each_file_pair_sorted do |filename, fileinfo|
    state[filename] = {:lines    => SCRIPT_LINES__[filename], :coverage => fileinfo.coverage.to_a,:counts   => fileinfo.counts}
  end
  File.open(@state_file, "w") do |f|
    self.SERIALIZER.dump([FORMAT_VERSION, state], f)
  end
rescue
  $stderr.puts <<-EOF
  Couldn't save coverage data to #{@state_file}.
  EOF
end # '

def verify_diff_available

def verify_diff_available
  old_stderr = STDERR.dup
  old_stdout = STDOUT.dup
  new_stderr = Tempfile.new("rcov_check_diff")
  STDERR.reopen new_stderr.path
  STDOUT.reopen new_stderr.path
  retval = system "#{@diff_cmd} --version"
  unless retval
    old_stderr.puts <<EOF
e '#{@diff_cmd}' executable seems not to be available.
u can specify which diff executable should be used with --diff-cmd.
 your system doesn't have one, you might want to use Diff::LCS's:
m install diff-lcs
d use --diff-cmd=ldiff.
    return false
  end
  true
ensure
  STDOUT.reopen old_stdout
  STDERR.reopen old_stderr
  new_stderr.close!
end