
# Translate a unified patch (diff -ruN or cvs diff -uN for example)
# into an html page with a table of contents of the files (anchor #TOC).
# Largely taken from cvsweb.cgi by H. Zeller et al
# (http://linux.stud.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi/)

# Both diff_file() and diff_stdin() return the HTML contents to
# stdout, with the TOC at the end (your browser must have loaded the
# entire page before #TOC is available).

####
## Config variables
####
# Tabstop used to expand tabs in colored diffs. If undefined then
# tabs are always expanded to 8 spaces.
$tabstop = 8;

# when set, make lines breakable so that the columns do not
# exceed the width of the browser
$hr_breakable = 0;

# Colors and font to show the diff type of code changes
$diffcolorFname      = '#ffff00';  # color of the background for the file names headers
$diffcolorHeading    = '#99cccc';  # color of 'Line'-head of each diffed file
$diffcolorSame       = '#fdf5e6';  # color of unchanged lines
$diffcolorEmpty      = '#cccccc';  # color of 'empty' lines
$diffcolorRemove     = '#ff9999';  # Removed line(s) (left)  (  -  )
$diffcolorChange     = '#99ff99';  # Changed line(s) (     both    )
$diffcolorAdd        = '#ccccff';  # Added line(s)   (  - )  (right)
$diffcolorDarkChange = '#99cc99';  # lines empty in change
$diffcolorNofileFg   = '#ffffff';  # Text font color to indicate new/removed files
$difffontface        = "Helvetica,Arial";
$difffontsize        = "-1";

$state = "Invalid";

sub htmlify {
        my($string, $pr) = @_;

        # Special Characters; RFC 1866
        $string =~ s/&/&amp;/g;
        $string =~ s/\"/&quot;/g; 
        $string =~ s/</&lt;/g;
        $string =~ s/>/&gt;/g;

        # get URL's as link ..
        $string =~ s§(http|ftp)(://[-a-zA-Z0-9%.~:_/]+)([?&]([-a-zA-Z0-9%.~:_]+)=([-a-zA-Z0-9%.~:_])+)*§<A HREF="$1$2$3">$1$2$3</A>§;
        # get e-mails as link
        $string =~ s§([-a-zA-Z0-9_.]+@([-a-zA-Z0-9]+\.)+[A-Za-z]{2,4})§<A HREF="mailto:$1">$1</A>§;

        return $string;
}


sub urlencode {
    my ($in) = @_;
    my ($out);
    ($out = $in) =~ s/([\000-+{-\377])/sprintf("%%%02x", ord($1))/ge;
    return $out;
}


sub spacedHtmlText {
        my($string, $pr) = @_;

        # Cut trailing spaces
        s/\s+$//;

        # Expand tabs
        $string =~ s/\t+/' ' x (length($&) * $tabstop - length($`) % $tabstop)/e
            if (defined($tabstop));

        # replace <tab> and <space> (§ is to protect us from htmlify)
        # gzip can make excellent use of this repeating pattern :-)
        $string =~ s/§/§%/g; #protect our & substitute
        if ($hr_breakable) {
            # make every other space 'breakable'
            $string =~ s/	/ §nbsp; §nbsp; §nbsp; §nbsp;/g;    # <tab>
            $string =~ s/  / §nbsp;/g;                              # 2 * <space>
            # leave single space as it is
        }
        else {
            $string =~ s/	/§nbsp;§nbsp;§nbsp;§nbsp;§nbsp;§nbsp;§nbsp;§nbsp;/g; 
            $string =~ s/ /§nbsp;/g;
        }

        $string = htmlify($string);

        # unescape
        $string =~ s/§([^%])/&$1/g;
        $string =~ s/§%/§/g;

        return $string;
}


sub flush_diff_rows ($$$$)
{
    my $j;
    my ($leftColRef,$rightColRef,$leftRow,$rightRow) = @_;
    if ($state eq "PreChangeRemove") {          # we just got remove-lines before
      for ($j = 0 ; $j < $leftRow; $j++) {
          print  "<tr><td bgcolor=\"$diffcolorRemove\">@$leftColRef[$j]</td>";
          print  "<td bgcolor=\"$diffcolorEmpty\">&nbsp;</td></tr>\n";
      }
    }
    elsif ($state eq "PreChange") {             # state eq "PreChange"
      # we got removes with subsequent adds
      for ($j = 0; $j < $leftRow || $j < $rightRow ; $j++) {  # dump out both cols
          print  "<tr>";
          if ($j < $leftRow) {
              print  "<td bgcolor=\"$diffcolorChange\">@$leftColRef[$j]</td>";
          }
          else {
              print  "<td bgcolor=\"$diffcolorDarkChange\">&nbsp;</td>";
          }
          if ($j < $rightRow) {
              print  "<td bgcolor=\"$diffcolorChange\">@$rightColRef[$j]</td>";
          }
          else {
              print  "<td bgcolor=\"$diffcolorDarkChange\">&nbsp;</td>";
          }
          print  "</tr>\n";
      }
    }
}


##
# Function to generate Human readable diff-files
# human_readable_diff(String revision_to_return_to);
##
sub human_readable_diff($){
  my ($i,$difftxt);
  my ($fh) = @_;
  my (@rightCol, @leftCol);

  my $fs = "<font face=\"$difffontface\" size=\"$difffontsize\">";
  my $fe = "</font>";

  my $leftRow = 0;
  my $rightRow = 0;
  my ($oldline, $newline, $funname, $diffcode, $rest);

  my $fname_old;
  my $date_old;
  my $rev_old;
  my $fname_new;
  my $date_new;
  my $rev_new;

  my $rcs_index = "";

  my $file_anchor_id = 0;
  my @tab_anchors;
      
  # Process diff text
  # The diffrows are could make excellent use of
  # cascading style sheets because we've to set the
  # font and color for each row. anyone ...?
  ####
  $state = "NewFile";
  while (<$fh>) {
      $difftxt = $_;

      if (($state eq "NewFile") && ($difftxt =~ /^---/)) {
	  ($fname_old, $date_old, $rev_old)
	      = $difftxt =~ /^--- +([^\t]+)\t([^\t]+)\t?(.*)?$/;
      } elsif (($state eq "NewFile") && ($difftxt =~ /^Index: /)) {
	  ($rcs_index) = $difftxt =~ /^Index: (.*)$/;
      } elsif (($state eq "NewFile") && ($difftxt =~ /^\+\+\+/)) {
	  ($fname_new, $date_new, $rev_new)
	      = $difftxt =~ /^\+\+\+ +([^\t]+)\t([^\t]+)\t?(.*)?$/;
	  if (($fname_new eq "/dev/null") || ($fname_old eq "/dev/null"))
	  {
	      $fname_new = $fname_old = $rcs_index;
	      $date_old = $date_new = "";
	      $rev_new = $rev_old = "";
	  }
	  $tab_anchors[$file_anchor_id] = $fname_new;
	  $file_anchor_id++;
	  print  "\n\n<a name=\"$file_anchor_id\"><!-- File $fname_old vs. $fname_new -->\n";
	  print "&nbsp;<p><table cellspacing=0 cellpadding=0 border=0 width=100%>\n";
	  print "<tr bgcolor=$diffcolorFname>\n";
	  print "<td width=\"50%\">\n";
          print  "<table width=100% border=0 cellpadding=5><tr><td>";
	  print "<b>$fname_old</b>";
	  print " <font size=\"-1\">($date_old)</font>" if ($date_old ne "");
	  print "</td></tr><tr><td><font size=\"-1\">rev. $rev_old</font>"
	      if ($rev_old ne "");
          print  "</td></tr></table></td>";
	  print "<td width=\"50%\">\n";
          print  "<table width=100% border=0 cellpadding=5><tr><td  align=\"right\">";
	  print "<b>$fname_new</b>";
	  print " <font size=\"-1\">($date_new)</font>" if ($date_new ne "");
	  print "</td></tr><tr><td align=\"right\"><font size=\"-1\">rev. $rev_new</font>"
	      if ($rev_new ne "");
          print  "</td></tr></table></td>";
	  print "</tr>\n";
	  $state = "dump";
      } elsif ($difftxt =~ /^@@/) {
          ($oldline,$newline,$funname) = $difftxt =~ /@@ \-([0-9]+).*\+([0-9]+).*@@(.*)/;
	  print  "\n<!-- Line $oldline vs. $newline -->\n";
          print  "<tr bgcolor=\"$diffcolorHeading\"><td width=\"50%\">\n";
	  print  "<table width=100% border=1 cellpadding=2><tr><td align=\"center\">";
	  if ($oldline ne "0")
	  {
	      print "<b>Line $oldline</b>";
	  } else {
	      print "<font color=\"$diffcolorNofileFg\"><b>(New file)</b></font>";
	  }
	  print  "&nbsp;<font size=-1>$funname</font></td></tr></table>\n";
          print  "</td><td width=\"50%\">\n";
          print  "<table width=100% border=1 cellpadding=2><tr><td align=\"center\">";
	  if ($newline ne "0")
	  {
	      print "<b>Line $newline</b>";
	  } else {
	      print "<font color=\"$diffcolorNofileFg\"><b>(File removed)</b></font>";
	  }
          print  "&nbsp;<font size=-1>$funname</font></td></tr></table>\n";
          print  "</td></tr>\n\n";
	  $rcs_index = "";
          $state = "dump";
          $leftRow = 0;
          $rightRow = 0;
      }
      else {
          ($diffcode,$rest) = $difftxt =~ /^([-+ ])(.*)/;
          $_ = spacedHtmlText ($rest);

          # Add fontface, size
          $_ = "$fs&nbsp;$_$fe";
          
          #########
          # little state machine to parse unified-diff output (Hen, zeller@think.de)
          # in order to get some nice 'ediff'-mode output
          # states:
          #  "dump"             - just dump the value
          #  "PreChangeRemove"  - we began with '-' .. so this could be the start of a 'change' area or just remove
          #  "PreChange"        - okey, we got several '-' lines and moved to '+' lines -> this is a change block
          ##########

          if ($diffcode eq '+') {
              if ($state eq "dump") {  # 'change' never begins with '+': just dump out value
                  print  "<tr><td bgcolor=\"$diffcolorEmpty\">&nbsp;</td><td bgcolor=\"$diffcolorAdd\">$_</td></tr>\n";
              }
              else {                   # we got minus before
                  $state = "PreChange";
                  $rightCol[$rightRow++] = $_;
              }
          } 
          elsif ($diffcode eq '-') {
              $state = "PreChangeRemove";
              $leftCol[$leftRow++] = $_;
	  }
	  else {
	      flush_diff_rows \@leftCol, \@rightCol, $leftRow, $rightRow;
	      print  "<tr><td bgcolor=\"$diffcolorSame\">$_</td><td bgcolor=\"$diffcolorSame\">$_</td></tr>\n"
		  if ($state eq "dump");
	      if ($diffcode eq ' ') {  # empty diffcode
		  $state = "dump";
	      } else { # No diffcode
		  if ($difftxt =~ /^Index: /) {
		      ($rcs_index) = $difftxt =~ /^Index: (.*)$/;
		  }
		  print "\n<!-- End file $fname_old vs. $fname_new -->\n</table>\n"
		      if ($state ne "NewFile");
		  $state = "NewFile";
	      }
	      $leftRow = 0;
	      $rightRow = 0;
          }
      }
  }
  flush_diff_rows \@leftCol, \@rightCol, $leftRow, $rightRow;

  # state is empty if we didn't have any change
  if (!$state) {
      print "<table width=100%><tr><td colspan=2>&nbsp;</td></tr>";
      print "<tr bgcolor=\"$diffcolorEmpty\" >";
      print "<td colspan=2 align=center><b>- No viewable Change -</b></td></tr></table";
  }
  print  "\n\n<!-- =========== End of diff =========== -->\n</table>\n\n";
  close($fh);

  print "<br><hr noshade width=100%>\n";

  # print legend
  print "<table border=1><tr><td>";
  print  "<b>Legend:</b><br>&nbsp<br><table border=0 cellspacing=0 cellpadding=1>\n";
  print  "<tr bgcolor=\"$diffcolorSame\"><td align=center colspan=2>identical lines</td></tr>";
  print  "<tr><td align=center bgcolor=\"$diffcolorRemove\">Removed from old</td><td bgcolor=\"$diffcolorEmpty\">&nbsp;</td></tr>";
  print  "<tr bgcolor=\"$diffcolorChange\"><td align=center colspan=2>changed lines</td></tr>";
  print  "<tr><td bgcolor=\"$diffcolorEmpty\">&nbsp;</td><td align=center bgcolor=\"$diffcolorAdd\">Added in new</td></tr>";
  print  "</table></td></tr></table>\n";

  # print TOC
  print "\n<p><a name=\"TOC\"><b>File list:</b><br>\n";
  $file_anchor_id = 0;
  foreach $a (@tab_anchors) {
      $file_anchor_id ++;
      print "&nbsp;&nbsp;&nbsp;&nbsp;<a href=\"#$file_anchor_id\">$a</a><br>\n";
  }
}


sub dodiff_file
{
    my ($fname) = @_;
    my $fh = do {local(*FH);};
    
    open($fh, "<$fname") || die "Error open $fname\n";
    &human_readable_diff($fh);
}

sub dodiff_stdin
{
    &human_readable_diff(STDIN);
}

# &dodiff_file("p");
&dodiff_stdin();
