LXR Cross Referencer Git repo
Brought to you by:
ajlittoz
#!/usr/bin/perl -T ###################################################################### # # diff -- Display diff output with markup. # # Arne Georg Gleditsch <argggh@ifi.uio.no> # Per Kristian Gjermshus <pergj@ifi.uio.no> # # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # ###################################################################### use strict; use lib do { $0 =~ m{(.*)/} ? "$1/lib" : "lib" }; # if LXR modules are in ./lib =head1 diff script This script manages display of code differences between two versions of a source file. =cut use LXR::Common; use LXR::Markup; use LXR::Template; use Local; use FileHandle; =head2 C<fflush ()> Function C<fflush> sets STDOUT in autoflush mode. B<Note:> =over =item I<The reason for using this function is not clear. It has been commented out without adverse effect.> I<Being very short, it could be inlined (only one usage!) if it needs to be reenabled.> =back =cut # sub fflush { # $| = 1; # print(''); # } =head2 C<htmljust ($s, $w)> Function C<htmljust> returns an HTML string justified to exactly a fixed number of screen positions. =over =item 1 C<$s> a I<string> containing an HTML sequence =item 2 C<$w> an I<integer> defining the justification width =back The string argument is truncated or expanded to show exactly C<$w> "characters" on screen. Atomic units must not be split, otherwise HTML integrity is broken. HTML tags and entity references are copied without truncation. When checking overflow, HTML tags are considered as zero-width "characters" and HTML entity references as one screen position glyphs (which is not always the case: combining diacritic marks, zero-width spacers, ...). When the desired width is met, opening tags may not have been matched by their closing tags. To return a synctactically correct HTML sequence, HTML tags are still copied but without their content. This results in a sequence longer than necessary, but it is safe. =cut sub htmljust { my ($s, $w) = @_; my @s = split(/(<.*?>|&[\#\w\d]+;)/, $s); $s = ''; while (@s){ my $f = shift(@s); next if $f eq ''; if ('<' eq substr($f, 0, 1)) { # HTML tag element: no screen position, copy it $s .= $f } elsif ('&' eq substr($f, 0, 1)) { # HTML entity reference: one screen position usually # Copy it space permitting if ($w > 0) { $s .= $f; $w--; } } else { # Ordinary text, check for truncation $f = substr($f, 0, $w); $w -= length($f); $s .= $f; } } # Add spaces up to the requested width $s .= ' ' x $w; return $s; } =head2 C<printdiff (@dargs)> Procedure C<printdiff> is the main driver for difference display (two passes). =over =item 1 C<@dargs> an I<array> containing the C<'variables'> values for the reference version =back When entered for the first time, query arguments only offer current C<'variables'> values. This is detected by the absence of any C<~>I<var_name>C<=>... argument. Current values are then transfered into these so-called I<remembered> values and user is requested to choose another version. On second entry, both current values (I<var_name>C<=>...) and remembered values (C<~>I<var_name>C<=>...) are present in the query arguments. The latter values designate the reference version (in the right pane); the former values the "new" version (in the left pane). With these two file descriptions, processing can be done. The file name in C<$pathname> has been nominally transformed by the C<'maps'> rules. But to get the other name, we must first reverse the effects of these rules (in the remembered environment) et re-apply them (in the current environment). Once this is done, both file names correctly point to the desired versions. Next, physical (real) files are obtained so that I<rcs B<diff>> can build the patch directives.. Both files are highlighted by C<markupfile>. The resulting HTML streams are kept in memory. I<This could cause a serious strain on memory and degrade performance (because of swapping for instance).> Then it is relatively simple to merge both streams line by line under control of the patch directives. =cut sub printdiff { my (@dargs) = @_; if ($#dargs < 0) { # First pass through the script # Request second version my @vars; foreach ($config->allvariables) { if (!exists($config->{'variables'}{$_}{'when'}) || eval($config->varexpand($config->{'variables'}{$_}{'when'})) ) { push(@vars, $config->vardescription($_)); } } $vars[ $#vars - 1 ] .= ' or ' . pop(@vars) if $#vars > 0; print ( "<p align=\"center\">\n" , "Please indicate the version of the file you wish to\n" , "compare to by clicking on the appropriate\n" , join(', ', @vars) , " button.\n" , "</p>\n" ); return; } # Second pass - both versions are known if ('/' eq substr($pathname, -1)) { print("<h3 align=\"center\">Diff not yet supported for directories.</h3>\n"); return; } my $origname = $pathname; # Tentatively reverse the effect of mappath on $pathname to get an "early bird" # skeleton path on which to apply the mapping rules in the current environment. my $diffname = $config->mappath($config->unmappath($pathname, @dargs)); my ($diffv) = grep(m/v=/, @dargs); $diffv =~ s/v=//; unless ($files->isfile($origname, $diffv)) { print("<p class='error'>*** $origname does not exist in version $diffv ***</p>\n"); return; } unless ($files->isfile($diffname, $releaseid)) { print("<p class='error'>*** $diffname does not exist in version $releaseid ***</p>\n"); return; } # fflush; # realfilename may create a temporary file # which should be released when no longer needed my $origtemp = $files->realfilename($origname, $diffv); my $difftemp = $files->realfilename($diffname, $releaseid); $ENV{'PATH'} = '/usr/local/bin:/usr/bin:/bin:/usr/sbin'; unless (open(DIFF, '-|')) { open(STDERR, '>&STDOUT'); exec('diff', '-U0', $difftemp, $origtemp); die "*** Diff subprocess died unexpectedly: $!\n"; } my ($leftstart, $leftlen); # What is replaced in left file my ($rightstart, $rightlen); # What replaces in tight file my $facing; # Number of facing lines my $rightxcess; # Running count of lines in excess at right my $leftorg; # Final real line number at left my $dir; # Change indicator my %chg; # All change indicators my $blanks; # Number of blanks lines to keep abreast my (%leftblanks, %rightblanks); while (<DIFF>) { if (($leftstart, $leftlen, $rightstart, $rightlen) = m/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/ ) { $leftstart++ if $leftlen eq '0'; $rightstart++ if $rightlen eq '0'; $leftlen = 1 unless defined($leftlen); $rightlen = 1 unless defined($rightlen); $leftorg = $leftstart + $rightxcess; if ($leftlen < $rightlen) { $rightxcess += $rightlen - $leftlen; $dir = '>>'; $blanks = $rightlen - $leftlen; $facing = $leftlen; $leftblanks{$leftstart + $leftlen} = $blanks; } else { $dir = '<<'; $blanks = $leftlen - $rightlen; $facing = $rightlen; $rightblanks{$rightstart + $rightlen} = $blanks; } foreach (0 .. $facing - 1) { $chg{ $leftorg + $_ } = '!!'; } foreach (0 .. $blanks - 1) { $chg{ $leftorg + $facing + $_ } = $dir; } } } close(DIFF); # Print a descriptive title and tell exactly what versions # are compared (dump the variable value sets) my @linkargs = grep {m/(.*?)=(.*)/; $config->variable($1) ne $2;} @dargs; map (s/(.*?)=/!$1=/, @linkargs); print ( "<h1>Diff markup</h1>\n" , '<h2>between ' , fileref ( $diffname , 'diff-fref' , $diffname , undef , @linkargs ) , ' <small>(' ); my @fctx; for my $var ($config->allvariables) { next if exists($config->{'variables'}{$var}{'when'}) && !eval($config->varexpand($config->{'variables'}{$var}{'when'})); push (@fctx, $config->vardescription($var).': '.$config->variable($var)); } print ( join(', ', @fctx) , ')</small><br>' , ' and ' , fileref ( $origname , 'diff-fref' , $origname ) , ' <small>(' ); @fctx = (); for my $var ($config->allvariables) { next if exists($config->{'variables'}{$var}{'when'}) && !eval($config->varexpand($config->{'variables'}{$var}{'when'})); my ($varval) = grep(m/$var=/, @dargs); $varval =~ s/$var=//; push (@fctx, $config->vardescription($var).': '.$varval); } print ( join(', ', @fctx) , ")</small></h2><hr>\n" ); # Highlight both files my $origh = FileHandle->new($origtemp); # Save current environment before switching to @dargs environment my %oldvars; foreach my $arg (@dargs) { if ($arg =~ m/(.*?)=(.*)/) { $oldvars{$1} = $config->variable($1); $config->variable($1, $2); } } my $rightfile; markupfile($origh, sub { $rightfile .= shift }); # Restore original environment while ((my $var, my $val) = each %oldvars) { $config->variable($var, $val); } %oldvars = {}; $origh->close; $files->releaserealfilename($origtemp); $pathname = $diffname; my $diffh = FileHandle->new($difftemp); my $leftfile; markupfile($diffh, sub { $leftfile .= shift }); my $len = $. + $rightxcess; # Total lines displayed $diffh->close; $files->releaserealfilename($difftemp); $pathname = $origname; # Output both versions side by side my $i; $i = 0; $leftfile =~ s/^/"\n" x ($leftblanks{$i++})/mge; $i = 0; $rightfile =~ s/^/"\n" x ($rightblanks{$i++})/mge; my @leftlines = split(/\n/, $leftfile); my @rightlines = split(/\n/, $rightfile); my $leftwidth = $$HTTP{'param'}{'_diffleftwidth'} || $config->{'diffleftwidth'} || 50; print("<pre class=\"filecontent\">\n"); foreach $i (1 .. $len) { my $l = htmljust($leftlines[$i], $leftwidth); my $r = $rightlines[$i]; my $diffmark = ' '; if ($chg{$i}) { $diffmark = '<span class="diff-mark">' . $chg{$i} . "</span>"; if ('<<' eq $chg{$i}) { $l =~ s|</a> |</a> <span class="diff-left">|; } if ('>>' eq $chg{$i}) { $r =~ s|</a> |</a> <span class="diff-right">|; } if ('!!' eq $chg{$i}) { $l =~ s|</a> |</a> <span class="diff-both">|; $r =~ s|</a> |</a> <span class="diff-both">|; } $l .= '</span>'; $r .= '</span>'; } print "$l $diffmark $r\n"; } print("</pre>\n"); } =head2 Script entry point Builds the header and footer and launches C<printdiff> for the real job. =cut httpinit(); std_http_headers('diff'); makeheader('diff'); my @dargs; foreach my $param (keys %{$HTTP->{'param'}}) { my $var = $param; next unless $var =~ s/^~//; if (exists($config->{'variables'}{$var})) { push @dargs, "$var=" . $HTTP->{'param'}{$param}; } } printdiff(@dargs); makefooter('diff'); httpclean;