#!perl #--------------------------------------------------------------------- # vernum.pl 1.7 1998/11/04 18:24:01 Madsen Exp # Copyright 1996 Christopher J. Madsen # # 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, 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 Perl; see the file COPYING. If not, write to the # Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. # # Insert version numbers in files # (See documentation at end of this file or use pod2man.) #--------------------------------------------------------------------- require 5.000; use strict; use File::Basename; use Getopt::Mixed; my @months = qw(* Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); my $verDigits = 2; # Number of digits after decimal point my $version; my $date; #===================================================================== Getopt::Mixed::getOptions( 'digits=i d>digits', 'no-master n>no-master', 'keep-paths p>keep-paths', 'help ?>help version' ); usage(0) if $::opt_help or $::opt_help; if ($::opt_version or $::opt_version) { print 'VerNum 1.7 ',"\n"; exit 0; } if (defined $::opt_digits) { $verDigits = $::opt_digits; $verDigits = '' unless $verDigits > 1; } usage(1) unless $#ARGV > 0; # Must have at least 2 arguments foreach (@ARGV) { tr|\\|/| } # Convert backslashes my $outDir = shift @ARGV; $outDir =~ s|/$||; # Delete trailing slash die "$outDir does not exist" unless -d $outDir; my $masterFile = shift @ARGV; my $masterBase = basename($masterFile); my $outFile = "$outDir/" . ($::opt_keep_paths ? $masterFile : $masterBase); $outFile = "/dev/null" if $::opt_no_master or $::opt_no_master; open(IN, "<$masterFile") or die "Can't open $masterFile"; open(OUT,">$outFile") or die "Can't write to $outFile"; print "Processing $masterFile...\n"; while () { if (/\$Id:/) { s!\$Id\: [^ ]+ ([0-9.]+) ([0-9/]{10}) ([0-9:]{8}) .+?\$! sprintf("%s %s (%s %s GMT)", $masterBase, parseVer($1), parseDate($2), $3)!e or die "Invalid \$Id\$ string"; } s/\$Revision\: (.+?) \$/parseVer($1)/ge; s!\$Date\: ([0-9/]+).+?\$!parseDate($1)!ge; next if /^\$\$/; # $$ indicates comment s/\$\%\s*(.+?)\s*\%\$/processCode($1)/ge; print OUT; } # end while close IN; close OUT; utime((stat($masterFile))[8,9], $outFile); # Restore original timestamp #--------------------------------------------------------------------- my $file; foreach $file (@ARGV) { my $outFile = "$outDir/" . ($::opt_keep_paths ? $file : basename($file)); open(IN, "<$file") or die "Can't open $file"; open(OUT,">$outFile") or die "Can't write to $outFile"; print "Processing $file...\n"; while () { next if /^\$\$/; # $$ indicates comment s/\$\%\s*(.+?)\s*\%\$/processCode($1)/ge; print OUT; } # end while close IN; close OUT; utime((stat($file))[8,9], $outFile); # Restore original timestamp } # end foreach exit; #===================================================================== # Subroutines #--------------------------------------------------------------------- # Parse an RCS date field: # # Dies if the date does not match a previously seen date. # # Input: # The date in yyyy/mm/dd format # # Returns: # The date in d-mmm-yyyy format sub parseDate { my $dateStr = shift; my ($year,$month,$day) = split(m|/|, $dateStr); $dateStr = sprintf("%d-%s-%s",$day, $months[$month], $year); die "Dates do not match" if $date and $date ne $dateStr; $date = $dateStr; } # end parseDate #--------------------------------------------------------------------- # Parse a RCS version number field: # # Dies if the version number does not match a previously seen version # number. # # Input: # The RCS revision number # # Returns: # The version number sub parseVer { my $verStr = shift; $verStr =~ m/^(\d+)\.(\d+)([0-9.]*)$/ or die; $verStr = sprintf("%d.%0${verDigits}d%s",$1,$2,$3); die "Versions do not match" if $version and $version ne $verStr; $version = $verStr; } # end parseVer #--------------------------------------------------------------------- # Process a special code: # # Input: # The code minus the identifying marks and whitespace # # Returns: # The text that should be substituted sub processCode { my $code = shift; $code =~ s/([a-z])$/s/ or die "Invalid format"; my $data = do { if ($1 eq 'd') { $date } elsif ($1 eq 'v') { $version } else { die "Unknown code `$1'"} }; die "No data for code `$1'" unless $data; sprintf("%$code", $data); } # end processCode #--------------------------------------------------------------------- # Display usage information and exit: # # Input: # exit status (optional, default is 0) sub usage { my $exitStatus = shift; $exitStatus = 0 unless $exitStatus; print <<'EOT'; VerNum 1.7 Usage: vernum [options] DIRECTORY MASTER [FILE ...] -d, --digits=N Use at least N digits after the decimal point -n, --no-master Do not copy MASTER -p, --keep-paths Keep paths specified on command line -?, --help Display this usage information and exit --version Display version number and exit EOT exit $exitStatus; } # end usage __END__ =head1 NAME vernum - Replace RCS keywords and insert version numbers =head1 SYNOPSIS B [B<-p>] [B<-d width>] DIRECTORY MASTER [FILE ...] =head1 DESCRIPTION B copies I and any Is to I. While doing so, it formats RCS keywords in I (similar to B) and processes special codes in all files. It is normally used to reformat version numbers and to insert them in README files and other documentation. B processes the following RCS keywords in I: =over 10 =item C Replaced by the date in dd-Mmm-yyyy format. Single digit dates are not padded. =item C Replaced with "filename version (date time)". The version number and date are formatted as explained under the C and C keywords. =item C Replaced by the version number. The second part of the version number is left padded with zeros. The width of the second part is set by the B<-d> option (default 2). You can use B<-d1> to use the RCS version number unchanged. =back All other RCS keywords are left unchanged. B also processes special codes, which are delimited by `$%' and `%$'. Whitespace between the delimiters and the code is ignored. The only codes are: =over 4 =item B The date in dd-Mmm-yyyy format =item B The version number =back The values are taken from the RCS keywords C, C, and C in I. You can use B formatting codes to format the result: Example Result Notes |$%v%$| |1.03| Just like Revision keyword |$%-7v%$| |1.03 | Left justified in 7 char field |$%d%$| |1-Jan-1996| Just like Date keyword |$% 11d%$| | 1-Jan-1996| Right justified in 11 char field Furthermore, any line in Is beginning with `$$' is a comment and is removed. `$$' comments are not supported in I, since programming languages usually have their own comment facilities. =head1 OPTIONS =over 5 =item B<-d N>, B<--digits=N> Use at least I digits in the second part of version numbers. The default is 2. Use B<-d1> to keep the RCS version number unchanged. =item B<-p>, B<--keep-paths> Normally, B copies all files to I, regardless of their current location. The B<-p> option causes it to keep the paths specified on the command line (which must be relative paths). e.g., vernum dir lib/Getopt/Mixed.pm README will write to dir/lib/Getopt/Mixed.pm and dir/README. This will I create subdirectories; they must already exist. =item B<-?>, B<--help> Display usage information and exit. =item B<--version> Display version number and exit. =back =head1 REQUIREMENTS B requires Getopt::Mixed, available on CPAN as "CPAN"/authors/id/CJM/Getopt-Mixed-1.008.tar.gz =head1 BUGS I just hacked this together for my own use, because I got tired of having to update README files and other documents by hand. Thus, only features I've needed are included. Contact me if you want to add some more features. B does little error checking. Be careful. =head1 AUTHOR Christopher J. Madsen EFE