#!/usr/bin/perl # # Convert "cvs log" output to HTML. # - Cameron Simpson 19may2000 # =head1 NAME cvslog2html - transcribe CVS logs to HTML or plain text =head1 SYNOPSIS cvslog2html [{-D I|-r I}] [-t] [-O] [--diffs] [{-|I...}] =head1 DESCRIPTION I writes a CVS log as human friendly HTML or plain text to its standard output. If the sole non-option argument is a dash then I expects "cvs(1) log" output as its input. Otherwise the command: B I is run to obtain the log data. =cut use cs::Misc; use cs::HTML; use Getopt::Std; =head1 OPTIONS =over 4 =item B<-O> Obfuscate email addresses. Intended for things which are to be published on the web as a defense against address harvesting spiders. =item B<-r> I Passed to I. Produce the log since revision I. =item B<-t> Text mode - write plain text instead of HTML. =back =cut $TextMode=0; $ObfuscateEmail=0; $DoDiffs=0; $cvsopts=''; getopts("dD:r:tO") || die "$::cmd: bad options\n"; $DoDiffs=1 if defined $::opt_d; $TextMode=1 if defined $::opt_t; $ObfuscateEmail=1 if defined $::opt_O; $cvsopts="$cvsopt -r$::opt_r" if defined $::opt_r; $cvsopts="$cvsopt -D$::opt_D" if defined $::opt_D; if (@ARGV == 1 && $ARGV[0] eq '-') { if (-t STDIN) { die "$::cmd: I'm expecting \"cvs log\" data on my input, not a terminal!\n"; } } else { open(STDIN,"cvs log $cvsopts @ARGV |") || die "$0: can't pipe from \"cvs log @ARGV\": $!\n"; } while (defined ($_=)) { chomp; ::debug("[$_]\n"); if ($incomment) { if (/^=============================================================================$/ || /^----------------------------$/ ) { ::debug("END COMMENT"); $incomment=0; if (@comment) { push(@log,[$file, $rev, $day, $time, $author, [@comment]]); } } elsif (!@comments && /^branches:/) { ::debug("branch: while no comments"); } elsif (!@comments && ( $_ eq '*** empty log message ***' || /^initial (commit|check\s*in|revision)/i ) ) { ::debug("noise: $_\n"); } # Noise else { ::debug("add comment: $_\n"); push(@comment,$_); } } elsif (/^$/) { ::debug("forget $file\n"); undef $file; } elsif (! defined $file) { if (/^Working file:\s+/) { $file=$'; ::debug("new file: $file\n"); } else { ::debug("no current file: skip: $_\n"); } } else { if (/^revision (\d+\.\d+)$/) { $rev=$1; ::debug("set rev=$rev\n"); } elsif (/^date:\s+(\d\d\d\d)[^\d\s](\d\d)[^\d\s](\d\d)\s+(\d\d:\d\d:\d\d)[^;]*;\s+author:\s+(\w+);/) { $incomment=1; $day="$1-$2-$3"; $time=$4; $author=$5; @comment=(); ::debug("new change start: $_\n"); } else { ::debug("skip: $_\n"); } } } die "$::cmd: nothing in the CVS log!\n" if ! @log; undef $day; undef $auhtor; undef $file; undef $time; undef $ortxt; # old revision string if (! $TextMode) { my $pwd; chomp($pwd=`pwd`); my $htpwd = r2h($pwd); print "CVS log for $htpwd\n"; print "

CVS log for $htpwd

\n"; } if (! $TextMode) { print "
    \n"; } for my $L (reverse sort cmplogs @log) { my($f,$r,$d,$t,$a,$C)=@$L; if ($r eq '1.1') { unshift(@$C,"Initial checkin."); } if (! defined $day || $day ne $d) { if (defined $day && ! $TextMode) { print "\n"; } $day=$d; undef $author; undef $ortxt; if ($TextMode) { print "$day\n"; } else { print "
  • $day
    \n"; print "
    ";
        }
      }
    
      if (! defined $author || $author ne $a)
      { $author=$a;
        undef $file;
        print "    $author:\n";
      }
    
      for my $comment (@$C)
      {
        if ($ObfuscateEmail)
        { $comment =~ s/(\S)\@(\S+)/$1-at-$2/g;
        }
    
        if (! defined $file || $file ne $f)
        { $file=$f;
          print   "      ".fref($file).": ";
          $indent="      ".(' ' x length($file))."  ";
        }
        else
        { print $indent;
        }
    
        print r2h($comment), "\n";
      }
    
      if ($DoDiffs && $r ne '1.1')
      {
        if (!open(DIFF,"cvsdiff-rev -r $r '$f' | sed -n '/^[+\-]/p' |"))
        { warn "$::cmd: can't get diff for $f, revision $r\n";
        }
        else
        { while (defined($_=))
          { chomp;
    	print $indent, "  ", r2h($_), "\n";
          }
          close(DIFF);
        }
    
        undef $file;
      }
    }
    
    if (! $TextMode)
    { print "
\n"; } sub r2h { $TextMode ? "@_" : &cs::HTML::raw2html } sub fref { my($file)=@_; $TextMode ? $file : cs::HTML::tok2a([A,{HREF=>$file},$file]); } sub cmplogs { my($f1,$r1,$d1,$t1,$a1)=@$a; my($f2,$r2,$d2,$t2,$a2)=@$b; my $daf1 = "$d1 $a1 $f1"; my $daf2 = "$d2 $a2 $f2"; $daf1 eq $daf2 ? $t2 cmp $t1 : $daf1 cmp $daf2 ; } sub revprev { my($rev)=@_; die "0: can't compute revprev($rev)" if $rev !~ /^(\d+)\.(\d+)/; return "$1.".($2-1); } =head1 SEE ALSO cvs(1) =head1 AUTHOR Cameron Simpson Ecs@zip.com.auE 19may2000 =cut