#! @PERL@ -w # @configure_input@ # Copyright © 2001,2003 by Martin Kammerhofer # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # Convert "cvs rdiff -s" output to HTML with links to cvsweb.cgi. # @(#)$Id: cvsrdiff2cvsweb.pl.in,v 1.17 2006/06/22 10:44:15 martin Exp $ require 5.003 ; use strict ; use Getopt::Long ; use Config::IniFiles ; use constant CONFENV => "CVSWEB_CONVERTERS_CONF" ; # ENV variable use constant CONFNAME => "cvsweb-converters.conf" ; # file basename use constant COMMON => "common" ; # default section in ini-file { local $^W = 0 ; eval "use URI::Escape" ; if ($@) { # provide a dummy function if package URI::Escape is not available. sub uri_escape { return $_[0] ; } warn "$0: package URI::Escape is not available\n" ; } } # URL-encode space and all control and hi-bit characters sub url_escape ($) { return uri_escape( shift, "\x00-\x20\x7f-\xff" ) ; } sub lookup_configfile() ; sub read_configfile ($@) ; delete @ENV{qw(IFS CDPATH ENV BASH_ENV LD_LIBRARY_PATH LD_PRELOAD)} ; $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin:@PREFIX@/bin' ; my ( $cvsweb, $urlsuffix, $branch, $repository, $rev1, $rev2, $outfile, $perldoc ) = # DO NOT EDIT THE DEFAULTS HERE - use the configfile instead! # (Otherwise you have to start all over when you upgrade this script.) ( "http://cvsweb.FreeBSD.org/", # URL of cvsweb.cgi "", # suffix to URL e.g. "cvsroot=myproject" "", # RCS branch e.g. "MAIN" "", "", "", # repo, r1, r2 "-", "perldoc", ) ; my %optctl = ( "cvsweb" => \$cvsweb, "urlsuffix" => \$urlsuffix, "branch" => \$branch, "output-file" => \$outfile, "repository" => \$repository, "rev1" => \$rev1, "rev2" => \$rev2, ) ; my $prog = $0 ; $prog =~ s+^.*/++ ; # basename $prog =~ s/\.p(er)?l$// ; # truncate '.pl' suffix my $version = '$Id: cvsrdiff2cvsweb.pl.in,v 1.17 2006/06/22 10:44:15 martin Exp $' ; # '; $version =~ s/^\s*\$Id: // ; $version =~ s/ \$\s*$// ; sub usage () { print STDERR "usage: $prog [--cvsweb=URL] [--urlsuffix=SFX] [--branch=TAG]", " [--output-file=FILE] rdiff-file\n", " or: $prog [--cvsweb=URL] [--urlsuffix=SFX] [--branch=TAG]", " [--output-file=FILE] --repository=REPO --rev1=REV1 --rev2=REV2 module...\n", " or: $prog --help\n", " or: $prog --version\n" ; exit 64 ; } sub help () { { exec $perldoc, $0 } ; print STDERR "$prog: cannot exec '$perldoc'\n" ; goto &usage ; } sub html_escape ($) { local $_ = shift or die ; s/\&/&/g ; s/\"/"/g ; s/>/>/g ; s/ $outfile" ) or die "$prog: redirect output to '$outfile': $!.\nStopped" ; } my $command ; if ($repository) { $command = "cvs -lq -d " . quotemeta($repository) . " rdiff -s" . " -r " . quotemeta($rev1) . " -r " . quotemeta($rev2) ; while ( my $module = shift(@ARGV) ) { $command .= " " . quotemeta($module) ; } $command = $1 if $command =~ /^(.*)$/ ; # untaint close(STDIN) or die ; open( STDIN, "$command |" ) or die "$prog: running command '$command': $!.\nStopped" ; } my ( @f, $fname, $uri ) ; $cvsweb =~ s!/+$!! ; # remove trailing slashes my $cvsweb_suffix = "" ; $cvsweb_suffix = "?only_with_tag=" . $branch if $branch ; $cvsweb_suffix .= ( $cvsweb_suffix ? "&" : "?" ) . $urlsuffix if $urlsuffix ; my $title = "$prog" ; $title .= " $ARGV[0]" if $#ARGV == 0 ; $title = html_escape($title) ; # paranoia print < $title
EndOfHeader
print "$command\n" if $command ;

while (<>) {
    next unless /^File / ;
    @f = split ;
    if ( $f[0] eq 'File' ) {

	# hyperlink the file name
	$fname = html_escape( $f[1] ) ;                        # be paranoid
	$uri   = url_escape("$cvsweb/$f[1]$cvsweb_suffix") ;
	s!\Q$f[1]\E!$fname! ;

	# search for "revision" "#"
	# or "revision" "#1" to "#2"
	for ( my $i = 2 ; $i < $#f ; $i++ ) {
	    next unless $f[$i] eq 'revision' ;
	    last unless $f[ ++$i ] =~ /^\d+(\.\d+)+$/ ;

	    # hyperlink the revision number
	    $uri =
		url_escape("$cvsweb/$f[1]") . "#"
	      . url_escape("rev$f[$i]$cvsweb_suffix") ;
	    s!\b\Q$f[$i]\E\b!$f[$i]! ;
	    last
	      unless $i + 2 <= $#f
	      && $f[ ++$i ] eq 'to'
	      && $f[ ++$i ] =~ /^\d+(\.\d+)+$/ ;

	    # hyperlink the second revision number
	    $uri =
		url_escape("$cvsweb/$f[1]") . "#"
	      . url_escape("rev$f[$i]$cvsweb_suffix") ;
	    s!\b\Q$f[$i]\E\b!$f[$i]! ;

	    # hyperlink the word "to" to the delta
	    my $suffix = ( $cvsweb_suffix ? "$cvsweb_suffix&" : "?" ) ;
	    $uri =
	      url_escape("$cvsweb/$f[1].diff${suffix}r1=$f[$i-2]&r2=$f[$i]") ;
	    s!\bto\b!to! ;
	    last ;
	}
    }
} continue {
    print ;
}

print <<'EndOfFooter';
EndOfFooter if ( $outfile ne "-" ) { close(STDOUT) or die ; } exit( $. ? 0 : 1 ) ; # it's an error if no lines have been read # ---------------------------------------------------------------------- # Look for a config file # # try 1. $ENV{CONFENV} (constants CONF* are defined above) # 2. "~/.CONFNAME" (in home directory prefixed with a dot) # and 3. "@PREFIX@/etc/CONFNAME" sub lookup_configfile () { if ( exists $ENV{ +CONFENV } ) { return $ENV{ +CONFENV } ; } elsif ( exists $ENV{HOME} && -e ( "$ENV{HOME}/." . CONFNAME ) ) { return "$ENV{HOME}/." . CONFNAME ; } elsif ( -e "@PREFIX@/etc/" . CONFNAME ) { return "@PREFIX@/etc/" . CONFNAME ; } return undef ; } # read defaults for commandline options from the config file # 1st argument is filename, other arguments are names of config variables sub read_configfile ($@) { my ( $configfile, @param_names ) = @_ ; map { $_ = $1 if /^(.*)/ } (@param_names) ; # untaint my %config ; if ( !tie %config, 'Config::IniFiles', ( -file => $configfile, -default => COMMON ) ) { $" = "\n" ; die "$0: cannot tie to config file '$configfile'\n", "@Config::IniFiles::errors\n" ; } # get my (sub)section(s) my ( @sections, %param_hash ) ; push @sections, $config{ +COMMON } if exists $config{ +COMMON } ; while ( my ( $section, $hashref ) = each %config ) { push @sections, $hashref if $section =~ m!\Q$prog\E!io ; } # read and assign my parameters my $parameter_count = 0 ; foreach my $name (@param_names) { $param_hash{$name} = undef ; # remember parameter names foreach my $section (@sections) { if ( exists $section->{$name} ) { eval qq! \$$name = \$section->{'$name'} ! ; die "$prog: cannot assign to \$$name: $@\n" if $@ ; $parameter_count++ ; } } } print STDERR "$prog: WARNING: no parameters read from '$configfile'\n" unless $parameter_count ; # warn about unrecognized parameters my @unrecognized ; foreach my $section (@sections) { foreach my $parameter ( keys %{$section} ) { push @unrecognized, $parameter unless exists $param_hash{$parameter} ; } } if (@unrecognized) { @unrecognized = sort @unrecognized ; print STDERR "$prog: WARNING: ", "the following parameters are not recognized:\n", "@unrecognized\n" ; } # untie untie %config or die "untie failed" ; } # end of sub "read_configfile" __END__; =head1 NAME cvsrdiff2cvsweb - convert a C output to HTML =head1 SYNOPSIS =over 4 =item * cvsrdiff2cvsweb [--cvsweb=I] [--urlsuffix=I] [--branch=I] [--output-file=I] I =item * cvsrdiff2cvsweb [--cvsweb=I] [--urlsuffix=I] [--branch=I] [--output-file=I] --repository=I --rev1=I --rev2=I I... =item * cvsrdiff2cvsweb --version =back =head1 DESCRIPTION The cvsrdiff2cvsweb program takes output from C (change summary) and converts it into HTML. Names of changed (added, updatet or deleted) files are replaced with hyperlinks to a C CGI script. This means you can click on any of the updated files and see the CVS log (change history) and have access to all the revisions and deltas. (The cgi-script C was originally written by Bill Fenner for the FreeBSD project. It allows browsing of CVS-repositories with a HTML-browser. CVS is a popular version control system.) Options may be abbreviated to a unique prefix. The options are as follows: =over 4 =item --cvsweb=I Specify URL of cvsweb.cgi script. =item --urlsuffix=I Specify some extra information for appending to generated URLs. (You should not type a leading C or C<&> character because it will be added automatically.) =item --branch=I Tell C that you are only interested in file revisions on the specified branch. =item --output-file=I Specify the output file. If no output file is specified standard output is used. =item --repository=I This is used to invoke the C command. This option requires options C<--rev1> and C<--rev2> too. =item --rev1=I --rev2=I Specify which revisions to compare. Unless C is set in your environment this will also require option C<--repository>. =item --version Print version information and exit. =back =head1 FILES C looks for a configuration file in three places. =over 4 =item * If the variable C is set in the environment its content is interpreted as the name of the configuration file, otherwise =item * the file F<~/.cvsweb-converters.conf> is examined, and finally =item * F<@PREFIX@/etc/cvsweb-converters.conf> is tried. =back Only the first found file is read. =head1 EXAMPLE Suppose you are running the FreeBSD operating and want to know in detail how the networking code changed between S and the latest 6-X-stable. C C Cnetinet.rdiff> C Now open F with your favorite browser! =head1 BUGS There is no provision to invoke C with custom options or dates (C<-D>) rather than revision numbers or tags. It is however possible to run C manually and feed the output into C. =head1 AUTHOR Martin Kammerhofer =cut # Local Variables: # mode: perl # End: #EOF#