example1-file1 Fri Aug 16 15:51:23 2002 |
example1-file2 Sun Jun 9 12:24:37 2002 |
#!/usr/bin/perl | #!/usr/bin/perl |
#------------------------------------------------------------------------------ | #------------------------------------------------------------------------------ |
# $Id: hdiff,v 1.7 2002/08/16 05:50:51 peters Exp $ | # $Id: hdiff,v 1.4 2002/06/09 02:18:18 peters Exp $ |
# | # |
# NAME: hdiff | # NAME: hdiff |
# | # |
# PURPOSE: HTML diff. Produces a colourised HTML diff output which is | # PURPOSE: HTML diff. Produces a colourised HTML diff output which is |
# much easier to read than standard diff output. | # much easier to read than standard diff output. |
# | # |
# SOURCE: http://www.ginini.com/software/hdiff/ | # SOURCE: www.ginini.com/software/hdiff |
# | # |
# hdiff is derived from cvs2html from www.sslug.dk/cvs2html | # hdiff is derived from cvs2html from www.sslug.dk/cvs2html |
# | # |
| # MODS: Thanks to Gordon McKinney to support recursion (-r) and |
| # multiple file files per diff run. Also fixed blank context |
| # lines removing the background fill. |
| # |
#------------------------------------------------------------------------------ | #------------------------------------------------------------------------------ |
| |
require 5.000; | |
| |
use Getopt::Std; | use Getopt::Std; |
| |
# | # |
# You must use a version of diff that has unified output. GNU diff has this | # You must use a version of diff that has unified output. GNU diff has this |
# option, so you can leave the default setting as is on Linux based systems. | # option, so you can leave the default setting as is on Linux based systems. |
# For most other Unix systems, grab a copy of GNU diff from | # For most other Unix systems, grab a copy of GNU diff from |
# ftp://ftp.gnu.org/gnu/diffutils | # ftp://ftp.gnu.org/gnu/diffutils |
# | # |
$diff = '/usr/bin/diff'; | $diff = 'diff'; |
| |
# | # |
# Colors and font to show the diff type of code changes | # Colors and font to show the diff type of code changes |
$diffcolorChange = '#99FF99'; # Changed line(s) ( both ) | $diffcolorChange = '#99FF99'; # Changed line(s) ( both ) |
$diffcolorRemove = '#CCCCFF'; # Added line(s) ( - ) (right) | $diffcolorRemove = '#CCCCFF'; # Added line(s) ( - ) (right) |
$diffcolorDarkChange = '#99CC99'; # lines, which are empty in change | $diffcolorDarkChange = '#99CC99'; # lines, which are empty in change |
$diffcolorLineNum = '#666666'; # Lines containing line numbers | |
| |
#----------------------------------------------------------------------------- | #----------------------------------------------------------------------------- |
# END OF CONFIGURABLE OPTIONS | # END OF CONFIGURABLE OPTIONS |
#----------------------------------------------------------------------------- | #----------------------------------------------------------------------------- |
| |
$Version = "2.1.0"; | $Version = "1.9.9"; |
| |
# | # |
# Process options | # Process options |
# | # |
| use vars qw($opt_s $opt_r); |
| |
# Keep warnings happy | getopts('h:f:t:rl:s') or Usage(); |
use vars qw($opt_s $opt_r $opt_n $opt_N); | |
| |
getopts('h:f:t:rl:sL:c:C:e:nNo:') or Usage(); | |
| |
Usage() unless ( scalar @ARGV == 2 ); | Usage() unless ( scalar @ARGV == 2 ); |
| |
$File1 = $ARGV[0]; | $File1 = $ARGV[0]; |
$File2 = $ARGV[1]; | $File2 = $ARGV[1]; |
| |
if ( defined $opt_o ) { | |
open STDOUT, ">$opt_o" | |
or die "Error creating file '$opt_o' ($!)"; | |
} | |
| |
$tabstop = ( $opt_e > 0 ? $opt_e : 8 ) if defined $opt_e; | |
| |
if ($opt_t) { | if ($opt_t) { |
$Title = "$opt_t"; | $Title = "$opt_t"; |
} | } |
$Title = "hdiff output"; | $Title = "hdiff output"; |
} | } |
| |
$opt_L = 'legend,Lines Added,Lines changed,Lines Removed,- No viewable Change -,Line ##NUM#' unless $opt_L; | |
| |
( $TextLegend, $TextAdded, $TextChanged, $TextRemoved, $TextNoChange, $TextLineNumber ) = map htmlify($_), split /,/, $opt_L . ", MISSING , MISSING , MISSING , MISSING , MISSING , MISSING "; | |
| |
$LeftCaptionText = ( $opt_c ? htmlify($opt_c) : '#NAME#' ); | |
$RightCaptionText = ( $opt_C ? htmlify($opt_C) : '#NAME#' ); | |
| |
die "$File1 not found\n" if ( ( !-f $File1 ) && ( !-d $File1 ) ); | die "$File1 not found\n" if ( ( !-f $File1 ) && ( !-d $File1 ) ); |
die "$File2 not found\n" if ( ( !-f $File2 ) && ( !-d $File2 ) ); | die "$File2 not found\n" if ( ( !-f $File2 ) && ( !-d $File2 ) ); |
| |
| |
HtmlFooter(); | HtmlFooter(); |
| |
| exit 0; |
| |
#-------------------------------------------------------------------------- | #-------------------------------------------------------------------------- |
sub htmlify { | sub htmlify { |
my ( $string, $pr ) = @_; | my ( $string, $pr ) = @_; |
s/\s+$//; | s/\s+$//; |
| |
# Expand tabs | # Expand tabs |
if ( defined $tabstop ) { | $string =~ s/\t+/' ' x (length($&) * $tabstop - length($`) % $tabstop)/e |
while ( $string =~ s/\t+/' ' x (length($&) * $tabstop - length($`) % $tabstop)/e ) { } | if ( defined $tabstop ); |
} | |
| |
# replace <tab> and <space> (§ is to protect us from htmlify) | # replace <tab> and <space> (§ is to protect us from htmlify) |
# gzip can make excellent use of this repeating pattern :-) | # gzip can make excellent use of this repeating pattern :-) |
} | } |
| |
#------------------------------------------------------------------------------ | #------------------------------------------------------------------------------ |
sub LineNumText { | |
my ($LineNum) = @_; | |
return '' unless $opt_n; | |
return spacedHtmlText( sprintf( "%3d:", ($$LineNum)++ ) ); | |
} | |
| |
#------------------------------------------------------------------------------ | |
sub flush_diff_rows { | sub flush_diff_rows { |
my $j; | my $j; |
my ( $leftColRef, $rightColRef, $leftRow, $rightRow ) = @_; | my ( $leftColRef, $rightColRef, $leftRow, $rightRow ) = @_; |
#------------------------------------------------------------------------------ | #------------------------------------------------------------------------------ |
sub HtmlHeader { | sub HtmlHeader { |
if ($opt_h) { | if ($opt_h) { |
my $header_content; | |
open HEADER, $opt_h or die "Can not open $opt_h $!\n"; | open HEADER, $opt_h or die "Can not open $opt_h $!\n"; |
$header_content = join ( "", <HEADER> ); | print <HEADER>; |
$header_content =~ s/#TITLE/$Title/g; | close (HEADER); |
print $header_content; | |
close(HEADER); | |
} | } |
else { | else { |
print qq(<html>\n<head>\n); | print qq(<html>\n<head>\n); |
print qq(<title>$Title</title>\n); | print qq(<title>$Title</title>\n); |
print qq(<style type="text/css">\n<!--\n); | print qq(<style type="text/css">\n<!--\n); |
print qq(BODY { font-family: Versana,Arial,Helvetica }\n); | print qq(BODY { font-family: Versana,Arial,Helvetica }\n); |
| |
if ($opt_s) { | if ($opt_s) { |
| |
| |
print <<EOF; | print <<EOF; |
<table border="0" cellspacing="0" cellpadding="1"> | <table border="0" cellspacing="0" cellpadding="1"> |
<tr><th align="center" bgcolor="#000000" colspan="2"><font color="#ffffff">$TextLegend</font></th></tr> | <tr><th align="center" bgcolor="#000000" colspan="2">Legend</th></tr> |
<tr><td align="center" bgcolor="$diffcolorRemove">$TextAdded</td><td bgcolor="$diffcolorEmpty"> </td></tr> | <tr><td align="center" bgcolor="$diffcolorRemove">Lines Added</td><td bgcolor="$diffcolorEmpty"> </td></tr> |
<tr bgcolor="$diffcolorChange"><td align="center" colspan="2">$TextChanged</td></tr> | <tr bgcolor="$diffcolorChange"><td align="center" colspan="2">Lines changed</td></tr> |
<tr><td bgcolor="$diffcolorEmpty"> </td><td align="center" bgcolor="$diffcolorAdd">$TextRemoved</td></tr> | <tr><td bgcolor="$diffcolorEmpty"> </td><td align="center" bgcolor="$diffcolorAdd">Lines Removed</td></tr> |
</table> | </table> |
| |
<p align="center"><font size="-1"><a href="http://www.ginini.com/software/hdiff/">hdiff - version: $Version</a></font></p> | <p align="center"><font size="-1"><a href="http://www.ginini.com/software/hdiff/">hdiff - version: $Version</a></font></p> |
if ($opt_f) { | if ($opt_f) { |
open FOOTER, $opt_f or die "Can not open $opt_f $!\n"; | open FOOTER, $opt_f or die "Can not open $opt_f $!\n"; |
print <FOOTER>; | print <FOOTER>; |
close(FOOTER); | close (FOOTER); |
return; | return; |
} | } |
else { | else { |
# Function to generate Human readable diff-files | # Function to generate Human readable diff-files |
# | # |
sub human_readable_diff { | sub human_readable_diff { |
my ( $file1, $file2 ) = @_; | my ($file1, $file2) = @_; |
| |
my ( $i, $difftxt, $filename, $pathname, $diffopts, $modefile ); | my ( $i, $difftxt, $filename, $pathname, $diffopts, $modefile ); |
| |
$diffopts .= "-r -N "; | $diffopts .= "-r -N "; |
} | } |
| |
if ( defined $opt_l ) { | if ($opt_l) { |
$diffopts .= "-U $opt_l "; | $diffopts .= "-U $opt_l "; |
} | } |
else { | else { |
# | # |
if ( $state eq "nodiff" ) { | if ( $state eq "nodiff" ) { |
print "<tr bgcolor=\"$diffcolorEmpty\" >"; | print "<tr bgcolor=\"$diffcolorEmpty\" >"; |
print "<td colspan=2 align=center><b>$TextNoChange</b></td></tr>"; | print "<td colspan=2 align=center><b>- No viewable Change -</b></td></tr>"; |
} | } |
| |
if ( $state ne "init" ) { | if ( $state ne "init" ) { |
# No state change, awaiting +++ | # No state change, awaiting +++ |
} | } |
elsif ( $difftxt =~ /^\+\+\+ / ) { | elsif ( $difftxt =~ /^\+\+\+ / ) { |
| |
($rightfile) = $difftxt =~ /^\+\+\+ (.*)/; | ($rightfile) = $difftxt =~ /^\+\+\+ (.*)/; |
| |
print qq(<table border="0" cellspacing="0" cellpadding="0" width="100%">\n); | print qq(<table border="0" cellspacing="0" cellpadding="0" width="100%">\n); |
print qq(<tr bgcolor="#000000">\n); | print qq(<tr bgcolor="#000000">\n); |
| |
$CaptionText = $LeftCaptionText; | |
$CaptionText =~ s/#NAME#/$leftfile/gi; | |
print qq(<th width="50%" valign="top">); | print qq(<th width="50%" valign="top">); |
print qq(<font color="#ffffff">$CaptionText</font>); | print qq(<font color="#ffffff">$leftfile</font>); |
print qq(</th>\n); | print qq(</th>\n); |
| |
$CaptionText = $RightCaptionText; | |
$CaptionText =~ s/#NAME#/$rightfile/gi; | |
print qq(<th width="50%" valign="top">); | print qq(<th width="50%" valign="top">); |
print qq(<font color="#ffffff">$CaptionText</font>); | print qq(<font color="#ffffff">$rightfile</font>); |
print qq(</th>\n</tr>); | print qq(</th>\n</tr>); |
| |
# Wait for next hunk (@@) | # Wait for next hunk (@@) |
print qq(<tr bgcolor="#000000">\n); | print qq(<tr bgcolor="#000000">\n); |
print qq(<th width="50%" valign="top">); | print qq(<th width="50%" valign="top">); |
print qq(<font color="#ffffff">$difftxt</font>); | print qq(<font color="#ffffff">$difftxt</font>); |
print qq(</th>\n); | print qq(<th>\n); |
print qq(<th width="50%" valign="top">); | print qq(<th width="50%" valign="top">); |
print qq(<font color="#ffffff"><!-- Only found in one directory --></font>); | print qq(<font color="#ffffff"><!-- Only found in one directory --></font>); |
print qq(</th>\n</tr>); | print qq(<th>\n</tr>); |
| |
# No state change, awaiting next 'Only in' or next --- | # No state change, awaiting next 'Only in' or next --- |
} | } |
} | } |
elsif ( $difftxt =~ /^@@/ ) { | elsif ( $difftxt =~ /^@@/ ) { |
| |
# In case of a number of context lines of 0 | |
flush_diff_rows \@leftCol, \@rightCol, $leftRow, $rightRow; | |
| |
# Hunk, start dumping | # Hunk, start dumping |
#( $funname ) = $difftxt =~ /@@ \-[0-9]+.*\+[0-9]+.*@@(.*)/; | #( $funname ) = $difftxt =~ /@@ \-[0-9]+.*\+[0-9]+.*@@(.*)/; |
$state = "dump"; | $state = "dump"; |
$leftRow = 0; | $leftRow = 0; |
$rightRow = 0; | $rightRow = 0; |
( $LeftLineNum, $RightLineNum ) = $difftxt =~ m/^@@ \-(\d+).*\+(\d+)/; | |
| |
if ($opt_N) { | |
my $LeftText = $TextLineNumber; | |
my $RightText = $TextLineNumber; | |
$LeftText =~ s/#NUM#/$LeftLineNum/gi; | |
$RightText =~ s/#NUM#/$RightLineNum/gi; | |
print "<tr><th width=\"50%\" bgcolor=\"$diffcolorLineNum\"><b>$LeftText</b></th>", "<th width=\"50%\" bgcolor=\"$diffcolorLineNum\">$RightText</th></tr>\n"; | |
} | |
| |
} | } |
else { | else { |
( $diffcode, $rest ) = $difftxt =~ /^([-+ ])(.*)/; | ( $diffcode, $rest ) = $difftxt =~ /^([-+ ])(.*)/; |
########## | ########## |
| |
if ( $diffcode eq '+' ) { | if ( $diffcode eq '+' ) { |
my $LineNumText = LineNumText($RightLineNum); | |
if ( $state eq "dump" ) { # 'change' never begins with '+': just dump out value | if ( $state eq "dump" ) { # 'change' never begins with '+': just dump out value |
print qq(<tr><td bgcolor="$diffcolorEmpty"> </td><td bgcolor="$diffcolorAdd">$LineNumText$_</td></tr>\n); | print qq(<tr><td bgcolor="$diffcolorEmpty"> </td><td bgcolor="$diffcolorAdd">$_</td></tr>\n); |
} | } |
else { # we got minus before | else { # we got minus before |
$state = "PreChange"; | $state = "PreChange"; |
$rightCol[ $rightRow++ ] = $LineNumText . $_; | $rightCol[ $rightRow++ ] = $_; |
} | } |
} | } |
elsif ( $diffcode eq '-' ) { | elsif ( $diffcode eq '-' ) { |
$state = "PreChangeRemove"; | $state = "PreChangeRemove"; |
$leftCol[ $leftRow++ ] = LineNumText($LeftLineNum) . $_; | $leftCol[ $leftRow++ ] = $_; |
} | } |
else { # empty diffcode | else { # empty diffcode |
flush_diff_rows \@leftCol, \@rightCol, $leftRow, $rightRow; | flush_diff_rows \@leftCol, \@rightCol, $leftRow, $rightRow; |
print "<tr><td>", LineNumText($LeftLineNum), "$_</td><td>", LineNumText($RightLineNum), "$_</td></tr>\n"; | print "<tr><td>$_</td><td>$_</td></tr>\n"; |
$state = "dump"; | $state = "dump"; |
$leftRow = 0; | $leftRow = 0; |
$rightRow = 0; | $rightRow = 0; |
# Only output for no diffs | # Only output for no diffs |
# | # |
if ( ( $state eq "nodiff" ) || ( $state eq "init" ) ) { | if ( ( $state eq "nodiff" ) || ( $state eq "init" ) ) { |
print qq(<tr bgcolor="$diffcolorEmpty" >); | print qq(tr bgcolor="$diffcolorEmpty" >); |
print qq(<td colspan="2" align="center"><b>$TextNoChange</b></td></tr>); | print qq(<td colspan="2" align="center"><b>- No viewable Change -</b></td></tr>); |
} | } |
| |
close(DIFF); | close(DIFF); |
| |
# In case diff does not return 0 (no diff) nor 1 (diffs) | |
die "Error running $diff $diffopts $file1 $file2\n" if ( $? >> 8 ) > 1; | |
| |
print "</table>\n\n"; | print "</table>\n\n"; |
print "<br><br>\n"; | print "<br><br>\n"; |
| |
} | } |
| |
#------------------------------------------------------------------------------ | #------------------------------------------------------------------------------ |
| # Display script usage |
sub Usage { | sub Usage { |
print <<EOF; | print <<EOF; |
Usage: $0 [-rsn] [-e <size>][-f <html footer>] [-h <html header>] [-l <lines>] [-L <Legend>,<Added>,<Changed>,<Removed>,<no change>,<Line>] [-t <title>] [-c <Text>] [-C <Text>] <file1/dir1> <file2/dir2> | Usage: $0 [-rs] [-f <html footer>] [-h <html header>] [-l <lines>] [-t <title>] <file1/dir1> <file2/dir2> |
| |
-c Caption for file 1 (#NAME# in caption is replaced by file name) | |
-C Caption for file 2 (#NAME# in caption is replaced by file name) | |
-e Expand tabs of 'size' characters (Defaults to 8 if <= 0) | |
-f File to use for HTML footer | -f File to use for HTML footer |
-h File to use for HTML header | -h File to use for HTML header |
-l Number of context lines | -l Num of context lines |
-n Show line numbers in front of each line | |
-N Show line numbers at the beginning of each block (in a separator line), | |
Put a '#NUM#' where you want to see the line number (otherwise it would not appear) | |
-o File to create instead of stdout | |
-r Recursively diff directories | |
-s Small font for printing | |
-t Title heading | |
-L Legends/Text appearing in output, defaults to: | |
"legend,Lines Added,Lines changed,Lines Removed,- No viewable Change -,Line ##NUM#" | |
| |
Version: $Version | Version: $Version |
| |