| 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 |
| | |