###########################################
# CVSUtil.pm -- Utility functions for CVS
# Mike Schilli, 2002 (m@perlmeister.com)
###########################################

package CVSUtil;

our $VERSION = "1.01";
our $CVSVERSION = '$Revision: 1.4 $';
our @ISA     = qw(Exporter);
our $CVS_COMMAND = "cvs";

use warnings;
use strict;
use File::Basename;

###########################################
sub cvs_command {
###########################################
    $CVS_COMMAND = $_[0] if defined $_[0];
    return $CVS_COMMAND;
}

###########################################
sub open_revision {
###########################################
    my ($path, $revision) = @_;

    my $cmd;
    my $cvsroot = cvsroot();

    if(!defined $revision) {
        # Get latest CVS version
        $revision = get_latest_revision($cvsroot, $path);
    }

    $path = get_cvs_path($path);

        # Get file
    $cmd = "$CVS_COMMAND -Q -d " . 
     "$cvsroot co -r $revision -p $path |";

    open my $fh, "$cmd" or 
                  die "Cannot open '$cmd'";
    return $fh;
}

###########################################
sub get_latest_revision {
###########################################
    my ($cvsroot, $path) = @_;

    my $cmd = "$CVS_COMMAND -Q -d " .
          "$cvsroot status $path" .
          " 2>/dev/null";
    open PIPE, "$cmd |" or 
         die "Cannot open pipe";
    my $data = join '', <PIPE>;
    close PIPE or die "$cmd failed";
    $data =~ /Repository revision:\s*([\d\.]+)/;
    return $1;
}

###########################################
sub get_cvs_path {
###########################################
    my ($file) = @_;

            # Get path within CVS/working path
    my $rep = File::Basename::dirname($file) . "/CVS/Repository";
    open FILE, "<$rep" or
          die "Cannot open $rep";
    chomp(my $rpath = <FILE>);
    $rpath .= "/" .  File::Basename::basename($file);
    close FILE;

    return $rpath;
}

###########################################
sub cvsroot {
###########################################

    open CVSROOT, "CVS/Root" or
        die "Cannot open CVS/Root";

    my $path = join '', <CVSROOT>;
    chomp $path;

    close CVSROOT;

    return $path;
}

###########################################
sub module {
###########################################

    open MODULE, "CVS/Repository" or
        die "Cannot open CVS/Repository";

    my $module = join '', <MODULE>;
    chomp $module;

    close MODULE;

    return $module;
}

1;

__END__

=head1 NAME

    CVSUtil -- Utility functions for CVS

=head1 DOWNLOAD

_SRC_HERE_

=head1 SYNOPSIS

    use CVSUtil;

    # NOTE: All of the following functions assume
    # That you've chdir()'d to some CVS working 
    # directory where the files specified reside.

        # Get a file handle to the latest checked-in
        # revision of test.c
    my $fh = CVSUtil::open_revision("test.c");
    while(<$fh>) { print $_; }
    close $fh;

        # Get a file handle to revision 1.1 of test.c
    my $fh = CVSUtil::open_revision("test.c", "1.1");
    while(<$fh>) { print $_; }
    close $fh;

        # Give the relative path (starting from the CVS
        # root) to a file in CVS.
    my $path = CVSUtil::get_cvs_path("test.c");
        # => 'modulename/dir1/dir2/test.c'

        # Get the revision number of the latest checked-in 
        # version of a file.
    my $rev = CVSUtil::get_latest_revision("test.c");
        # => '1.12'

        # Set the path to the 'cvs' program for
        # subsequent calls to CVSUtil functions
    CVSUtil::cvs_command("/tools/bin/cvs");

=head1 DESCRIPTION

A developer's CVS working directory often contains locally modified 
copies of checked-in versions of files. While the C<cvs diff> command
compares checked-in and local versions conveniently, there's no easy way
to the checked-in version itself.

C<CVSUtil> tries to bridge this gap by providing
an easy-to-use programming interface to figure
out which version of a file has been checked into CVS last and,
upon request, opens this revision and provides a reading 
file handle to it.

It uses cvs' local C<CVS/Root> and C<CVS/Repository> directories to 
find out about the CVSROOT of the current project and the relative
location of the current subdirectory within the project.

=head2 Functions

=over 4

=item CVSUtil::cvs_command( $path_to_cvs );

If your system doesn't have the C<cvs> command in your C<$PATH> variable
(i.e. if you can't type C<cvs> in your shell to call it), you need to tell
C<CVSUtil> first, where it's located, before you start using any of the 
commands listed below. The function returns the new setting of the
path to the cvs command. If called without parameters, it returns the
current setting.

=item my $fh = CVSUtil::open_revision($filename, [$rev]);

Opens revision C<$rev> of C<$filename> and returns a read-only file handle
to it. Note that you need to chdir() into a CVS working directory containing
the file C<$filename>, first. If C<$rev> is not specified, 
C<open_revision()> will use C<get_latest_revision()> to determine
the most recently checked-in version and provide a file handle to it.

=item my $rev = CVSUtil::get_latest_revision($filename);

Figures out the revision number of the most recently checked-in version
of the file $filename. Note that you need to chdir() into a CVS working 
directory containing the file C<$filename>, first. Returns the revision
number as a string (e.g. C<"1.12">) and C<undef> in case of an error.

=item my $path = CVSUtil::get_cvs_path($filename);

Determines the relative path to C<$filename>, starting from the CVS's
root directory. Note that you need to chdir() into a CVS working
directory containing the file C<$filename>, first.
E.g., if you're in C<projectname/dir1/dir2> and you call 
C<get_cvs_path("test.c")>, you'll get C<"projectname/dir1/dir2/test.c">
back, even if your local working directory is not C<projectname/dir1/dir2>
but something like C<myproject-1.1/dir1/dir2>.

=item my $module = CVSUtil::module();

Returns the name of the current module (project), which the 
current working directory belongs to.

=back

=head1 LEGALESE

Copyright 2002 by Mike Schilli, all rights reserved.
This program is free software, you can redistribute it and/or
modify it under the same terms as Perl itself.

=head1 AUTHOR

2002, Mike Schilli <m@perlmeister.com>
