summaryrefslogtreecommitdiff
path: root/cpan
diff options
context:
space:
mode:
authorfukachan <fukachan>2001-03-25 14:31:08 +0000
committerfukachan <fukachan>2001-03-25 14:31:08 +0000
commitbac6fcd72d2c54dd4ba78af90fcb2023a1a8e004 (patch)
tree0f1abc0e03e2998ceea82bba39a8d7e53dffb5bd /cpan
parentc31b61a2958dbfe2c6510684ba1d8e0be727ae56 (diff)
downloadfml8-bac6fcd72d2c54dd4ba78af90fcb2023a1a8e004.tar.gz
fml8-bac6fcd72d2c54dd4ba78af90fcb2023a1a8e004.tar.bz2
fml8-bac6fcd72d2c54dd4ba78af90fcb2023a1a8e004.zip
Initial revision
Diffstat (limited to 'cpan')
-rw-r--r--cpan/dist/Rcs/ANNOUNCE35
-rw-r--r--cpan/dist/Rcs/Changes5
-rw-r--r--cpan/dist/Rcs/MANIFEST36
-rw-r--r--cpan/dist/Rcs/Makefile.PL7
-rw-r--r--cpan/dist/Rcs/README30
-rw-r--r--cpan/dist/Rcs/Rcs.html658
-rw-r--r--cpan/dist/Rcs/Rcs.pm1343
-rwxr-xr-xcpan/dist/Rcs/examples/access.pl21
-rwxr-xr-xcpan/dist/Rcs/examples/ci-tree.pl46
-rwxr-xr-xcpan/dist/Rcs/examples/ci.pl28
-rwxr-xr-xcpan/dist/Rcs/examples/co-tree.pl45
-rwxr-xr-xcpan/dist/Rcs/examples/co.pl21
-rwxr-xr-xcpan/dist/Rcs/examples/comments.pl24
-rwxr-xr-xcpan/dist/Rcs/examples/comments2.pl24
-rwxr-xr-xcpan/dist/Rcs/examples/daterev.pl53
-rwxr-xr-xcpan/dist/Rcs/examples/dates.pl35
-rw-r--r--cpan/dist/Rcs/examples/green.txt85
-rwxr-xr-xcpan/dist/Rcs/examples/mark.pl21
-rwxr-xr-xcpan/dist/Rcs/examples/parse.pl38
-rw-r--r--cpan/dist/Rcs/examples/pause1
-rw-r--r--cpan/dist/Rcs/examples/project/src/Rcs.pm1320
-rw-r--r--cpan/dist/Rcs/examples/project/src/testfile2
-rwxr-xr-xcpan/dist/Rcs/examples/rcsclean.pl18
-rwxr-xr-xcpan/dist/Rcs/examples/rcsdiff.pl27
-rwxr-xr-xcpan/dist/Rcs/examples/revdate.pl28
-rwxr-xr-xcpan/dist/Rcs/examples/rlog.pl20
-rwxr-xr-xcpan/dist/Rcs/examples/symbols.pl23
-rwxr-xr-xcpan/dist/Rcs/examples/symrev.pl27
-rwxr-xr-xcpan/dist/Rcs/examples/test1.pl14
-rwxr-xr-xcpan/dist/Rcs/examples/test2.pl15
-rwxr-xr-xcpan/dist/Rcs/examples/test3.pl17
-rw-r--r--cpan/dist/Rcs/examples/type13
-rwxr-xr-xcpan/dist/Rcs/examples/unlock.pl20
-rw-r--r--cpan/dist/Rcs/test.pl20
-rw-r--r--cpan/dist/VCS-CVS/CVS.pm1474
-rw-r--r--cpan/dist/VCS-CVS/Changes.txt29
-rw-r--r--cpan/dist/VCS-CVS/MANIFEST8
-rw-r--r--cpan/dist/VCS-CVS/MANIFEST.SKIP3
-rw-r--r--cpan/dist/VCS-CVS/Makefile.PL29
-rw-r--r--cpan/dist/VCS-CVS/Readme.txt739
-rw-r--r--cpan/dist/VCS-CVS/t/base.t38
-rwxr-xr-xcpan/dist/VCS-CVS/test.pl276
-rw-r--r--cpan/lib/Rcs.pm1343
-rw-r--r--cpan/lib/VCS/CVS.pm1474
44 files changed, 9533 insertions, 0 deletions
diff --git a/cpan/dist/Rcs/ANNOUNCE b/cpan/dist/Rcs/ANNOUNCE
new file mode 100644
index 00000000..a0469142
--- /dev/null
+++ b/cpan/dist/Rcs/ANNOUNCE
@@ -0,0 +1,35 @@
+A recent programming project required the creation of a Revision Control
+System (RCS) front end. I started with inline system calls to the RCS
+utilities, and by creating multiple parsing functions to extract information
+from an RCS archive file.
+
+I then decided to put all my functions in one place, and created Perl module
+Rcs.pm.
+
+The next step was to change my functions into object methods, and Rcs.pm
+became object oriented.
+
+This Perl module should be of interest to anyone creating an RCS front end.
+
+Here's a quick and dirty example of how to check in a file.
+
+use Rcs;
+
+$obj = Rcs->new;
+$obj->file('testfile.pl');
+
+# Set RCS archive directory, is './RCS' by default
+$obj->rcsdir("./project_tree/archive");
+
+# Set working directory, is '.' by default
+$obj->workdir("./project_tree/src");
+
+$obj->ci('-mRevision Comment');
+
+Full documentation may be found at http://www.freter.com/perl/Rcs/Rcs.html
+
+If there's any interest, I would like to make Rcs.pm publicly available.
+Please let me know is you have any suggestions or comments.
+
+Craig Freter
+<craig@freter.com>
diff --git a/cpan/dist/Rcs/Changes b/cpan/dist/Rcs/Changes
new file mode 100644
index 00000000..4907cae2
--- /dev/null
+++ b/cpan/dist/Rcs/Changes
@@ -0,0 +1,5 @@
+Revision history for Perl extension Rcs.
+
+0.01 Mon Oct 19 14:09:04 1998
+ - original version; created by h2xs 1.18
+
diff --git a/cpan/dist/Rcs/MANIFEST b/cpan/dist/Rcs/MANIFEST
new file mode 100644
index 00000000..d770d7bb
--- /dev/null
+++ b/cpan/dist/Rcs/MANIFEST
@@ -0,0 +1,36 @@
+ANNOUNCE
+Changes
+MANIFEST
+Makefile.PL
+README
+Rcs.html
+Rcs.pm
+examples/project/RCS/testfile,v
+examples/project/RCS/Rcs.pm,v
+examples/project/src/Rcs.pm
+examples/project/src/testfile
+examples/access.pl
+examples/ci-tree.pl
+examples/ci.pl
+examples/co-tree.pl
+examples/co.pl
+examples/comments.pl
+examples/comments2.pl
+examples/daterev.pl
+examples/dates.pl
+examples/green.txt
+examples/mark.pl
+examples/parse.pl
+examples/pause
+examples/rcsclean.pl
+examples/rcsdiff.pl
+examples/revdate.pl
+examples/rlog.pl
+examples/symbols.pl
+examples/symrev.pl
+examples/test1.pl
+examples/test2.pl
+examples/test3.pl
+examples/type
+examples/unlock.pl
+test.pl
diff --git a/cpan/dist/Rcs/Makefile.PL b/cpan/dist/Rcs/Makefile.PL
new file mode 100644
index 00000000..62b7f389
--- /dev/null
+++ b/cpan/dist/Rcs/Makefile.PL
@@ -0,0 +1,7 @@
+use ExtUtils::MakeMaker;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+WriteMakefile(
+ 'NAME' => 'Rcs',
+ 'VERSION_FROM' => 'Rcs.pm', # finds $VERSION
+);
diff --git a/cpan/dist/Rcs/README b/cpan/dist/Rcs/README
new file mode 100644
index 00000000..c4aa6b2c
--- /dev/null
+++ b/cpan/dist/Rcs/README
@@ -0,0 +1,30 @@
+This is Perl Object Class Rcs.pm 0.09, a front-end to Revision Contron System (RCS)
+Utilities.
+
+New in Version 0.07:
+NT port by Jamie O'Shaughnessy, <jamie@thanatar.demon.co.uk>
+Added methods 'daterev', 'symrev', and 'symbols' contributed by Jamie O'Shaughnessy.
+Added method 'comments'.
+
+New in Version 0.08:
+Fixed bug parsing comments. Bug found by David Green <greendjf@cvhp152.gpt.co.uk>
+Modified daterev method to accept 1 or 6 arguments.
+
+New in Version 0.09:
+Fixed bug parsing comments. Bug found by David Green <greendjf@cvhp152.gpt.co.uk>
+
+Install Rcs.pm as you would any Perl 5 module:
+
+ perl Makefile.PL
+ make
+ make install
+
+The documentation is contained in Rcs.pm in pod format. The man pages
+will install automatically, or may be extracted manually by:
+
+ pod2man Rcs.pm > Rcs.man
+
+Please let me know is you have any suggestions or comments.
+
+Craig Freter
+<craig@freter.com>
diff --git a/cpan/dist/Rcs/Rcs.html b/cpan/dist/Rcs/Rcs.html
new file mode 100644
index 00000000..c576665c
--- /dev/null
+++ b/cpan/dist/Rcs/Rcs.html
@@ -0,0 +1,658 @@
+<HTML>
+<HEAD>
+<TITLE>Rcs - Perl Object Class for Revision Control System.</TITLE>
+<LINK REV="made" HREF="mailto:freter@postal.pcy.mci.net">
+</HEAD>
+
+<BODY>
+
+<!-- INDEX BEGIN -->
+
+<UL>
+
+ <LI><A HREF="#NAME">NAME</A>
+ <LI><A HREF="#SYNOPSIS">SYNOPSIS</A>
+ <LI><A HREF="#DESCRIPTION">DESCRIPTION</A>
+ <UL>
+
+ <LI><A HREF="#OBJECT_CONSTRUCTOR">OBJECT CONSTRUCTOR</A>
+ <LI><A HREF="#CLASS_METHODS">CLASS METHODS</A>
+ <LI><A HREF="#OBJECT_ATTRIBUTE_METHODS">OBJECT ATTRIBUTE METHODS</A>
+ <LI><A HREF="#RCS_PARSE_METHODS">RCS PARSE METHODS</A>
+ <LI><A HREF="#RCS_SYSTEM_METHODS">RCS SYSTEM METHODS</A>
+ </UL>
+
+ <LI><A HREF="#EXAMPLES">EXAMPLES</A>
+ <UL>
+
+ <LI><A HREF="#CREATE_ACCESS_LIST">CREATE ACCESS LIST</A>
+ <LI><A HREF="#PARSE_RCS_ARCHIVE_FILE">PARSE RCS ARCHIVE FILE</A>
+ <LI><A HREF="#CHECK_IN_FILE">CHECK-IN FILE</A>
+ <LI><A HREF="#CHECK_OUT_FILE">CHECK-OUT FILE</A>
+ <LI><A HREF="#RCSDIFF">RCSDIFF</A>
+ <LI><A HREF="#RCSCLEAN">RCSCLEAN</A>
+ </UL>
+
+ <LI><A HREF="#AUTHOR">AUTHOR</A>
+ <LI><A HREF="#CONTRIBUTORS">CONTRIBUTORS</A>
+ <LI><A HREF="#COPYRIGHT">COPYRIGHT</A>
+</UL>
+<!-- INDEX END -->
+
+<HR>
+<P>
+<H1><A NAME="NAME">NAME</A></H1>
+<P>
+Rcs - Perl Object Class for Revision Control System (RCS).
+
+<P>
+<HR>
+<H1><A NAME="SYNOPSIS">SYNOPSIS</A></H1>
+<P>
+<PRE> use Rcs;
+</PRE>
+<P>
+<HR>
+<H1><A NAME="DESCRIPTION">DESCRIPTION</A></H1>
+<P>
+This Perl module provides an object oriented interface to access
+<STRONG>Revision Control System (RCS)</STRONG> utilities. RCS must be installed on the system prior to using this module.
+This module should simplify the creation of an RCS front-end.
+
+<P>
+<HR>
+<H2><A NAME="OBJECT_CONSTRUCTOR">OBJECT CONSTRUCTOR</A></H2>
+<P>
+The <STRONG>new</STRONG> method may be used as either a class method or an object method to create a
+new object.
+
+<P>
+<PRE> # called as class method
+ $obj = Rcs-&gt;new;
+</PRE>
+<P>
+<PRE> # called as object method
+ $newobj = $obj-&gt;new;
+</PRE>
+<P>
+<HR>
+<H2><A NAME="CLASS_METHODS">CLASS METHODS</A></H2>
+<P>
+Besides the object constructor, there are three class methods provided
+which effect any newly created objects.
+
+<P>
+The <STRONG>arcext</STRONG> method sets the RCS archive extension, which is ',v' by default.
+
+<P>
+<PRE> # set/unset RCS archive extension
+ Rcs-&gt;arcext(''); # set no archive extension
+ Rcs-&gt;arcext(',v'); # set archive extension to ',v'
+ $arc_ext = Rcs-&gt;arcext(); # get current archive extension
+</PRE>
+<P>
+The <STRONG>bindir</STRONG> method sets the directory path where the RCS executables (i.e. rcs, ci, co)
+are located. The default location is '/usr/local/bin'.
+
+<P>
+<PRE> # set RCS bin directory
+ Rcs-&gt;bindir('/usr/bin');
+</PRE>
+<P>
+<PRE> # access RCS bin directory
+ $bin_dir = Rcs-&gt;bindir;
+</PRE>
+<P>
+The <STRONG>quiet</STRONG> method sets/unsets the quiet mode for the RCS executables. Quiet mode is
+set by default.
+
+<P>
+<PRE> # set/unset RCS quiet mode
+ Rcs-&gt;quiet(0); # unset quiet mode
+ Rcs-&gt;quiet(1); # set quiet mode
+</PRE>
+<P>
+<PRE> # access RCS quiet mode
+ $quiet_mode = Rcs-&gt;quiet;
+</PRE>
+<P>
+These methods may also be called as object methods.
+
+<P>
+<PRE> $obj-&gt;arcext('');
+ $obj-&gt;bindir('/usr/bin');
+ $obj-&gt;quiet(0);
+</PRE>
+<P>
+<HR>
+<H2><A NAME="OBJECT_ATTRIBUTE_METHODS">OBJECT ATTRIBUTE METHODS</A></H2>
+<P>
+These methods set the attributes of the RCS object.
+
+<P>
+The <STRONG>file</STRONG> method is used to set the name of the RCS working file. The filename must
+be set before invoking any access of modifier methods on the object.
+
+<P>
+<PRE> $obj-&gt;file('mr_anderson.pl');
+</PRE>
+<P>
+The <STRONG>arcfile</STRONG> method is used to set the name of the RCS archive file. Using this method
+is optional, as the other methods will assume the archive filename is the
+same as the working file unless specified otherwise. The RCS archive
+extension (default ',v') is automatically added to the filename.
+
+<P>
+<PRE> $obj-&gt;arcfile('principle_mcvicker.pl');
+</PRE>
+<P>
+The <STRONG>workdir</STRONG> methods set the path of the RCS working directory. If not specified,
+default path is '.' (current working directory).
+
+<P>
+<PRE> $obj-&gt;workdir('/usr/local/source');
+</PRE>
+<P>
+The <STRONG>rcsdir</STRONG> methods set the path of the RCS archive directory. If not specified,
+default path is './RCS'.
+
+<P>
+<PRE> $obj-&gt;rcsdir('/usr/local/archive');
+</PRE>
+<P>
+<HR>
+<H2><A NAME="RCS_PARSE_METHODS">RCS PARSE METHODS</A></H2>
+<P>
+This class provides methods to directly parse the RCS archive file.
+
+<P>
+The <STRONG>access</STRONG> method returns a list of all user on the access list.
+
+<P>
+<PRE> @access_list = $obj-&gt;access;
+</PRE>
+<P>
+The <STRONG>author</STRONG> method returns the author of the revision. The head revision is used if no
+revision argument is passed to method.
+
+<P>
+<PRE> # returns the author of revision '1.3'
+ $author = $obj-&gt;author('1.3');
+</PRE>
+<P>
+<PRE> # returns the authos of the head revision
+ $author = $obj-&gt;author;
+</PRE>
+<P>
+The <STRONG>head</STRONG> method returns the head revision.
+
+<P>
+<PRE> $head = $obj-&gt;head;
+</PRE>
+<P>
+The <STRONG>lock</STRONG> method returns the locker of the revision. The method returns null if the
+revision is unlocked. The head revision is used if no revision argument is
+passed to method.
+
+<P>
+<PRE> # returns locker of revision '1.3'
+ $locker = $obj-&gt;lock('1.3');
+</PRE>
+<P>
+<PRE> # returns locker of head revision
+ $locker = $obj-&gt;lock;
+</PRE>
+<P>
+The <STRONG>revisions</STRONG> method returns a list of all revisions of archive file.
+
+<P>
+<PRE> @revisions = $obj-&gt;revisions;
+</PRE>
+<P>
+The <STRONG>state</STRONG> method returns the state of the revision. The head revision is used if no
+revision argument is passed to method.
+
+<P>
+<PRE> # returns state of revision '1.3'
+ $state = $obj-&gt;state('1.3');
+</PRE>
+<P>
+<PRE> # returns state of head revision
+ $state = $obj-&gt;state;
+</PRE>
+<P>
+The <STRONG>symbol</STRONG> method returns the <CODE>symbol(s)</CODE> associated with a revision. If
+called in list context, method returns all symbols associated with
+revision. If called in scalar context, method returns last symbol assciated
+with a revision. The head revision is used if no revision argument is
+passed to method.
+
+<P>
+<PRE> # list context, returns all symbols associated with revision 1.3
+ @symbols = $obj-&gt;symbol('1.3');
+</PRE>
+<P>
+<PRE> # list context, returns all symbols associated with head revision
+ @symbols = $obj-&gt;symbol;
+</PRE>
+<P>
+<PRE> # scalar context, returns last symbol associated with revision 1.3
+ $symbol = $obj-&gt;symbol('1.3');
+</PRE>
+<P>
+<PRE> # scalar context, returns last symbol associated with head revision
+ $symbol = $obj-&gt;symbol;
+</PRE>
+<P>
+The <STRONG>symbols</STRONG> method returns a hash, keyed by symbol, of all of the revisions associated
+with the file.
+
+<P>
+<PRE> %symbols = $obj-&gt;symbols;
+ foreach $sym (keys %symbols) {
+ $rev = $symbols{$sym};
+ }
+</PRE>
+<P>
+The <STRONG>revdate</STRONG> method returns the date of a revision. The returned date format is the same
+as the localtime format. When called as a scalar, it returns the system
+date number. If called is list context, the list
+($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) is returned.
+
+<P>
+<PRE> # scalar mode
+ $scalar_date = $obj-&gt;revdate;
+ print &quot;Scalar date number = $scalar_date\n&quot;;
+ $date_str = localtime($scalar_date);
+ print &quot;Scalar date string = $date_str\n&quot;;
+</PRE>
+<P>
+<PRE> # list mode
+ @list_date = $obj-&gt;revdate;
+ print &quot;List date = @list_date\n&quot;;
+</PRE>
+<P>
+The <STRONG>dates</STRONG> method returns a hash of revision dates, keyed on revision. The hash values
+are system date numbers. When called in scalar mode, the method returns the
+most recent revision date.
+
+<P>
+<PRE> # list mode
+ %DatesHash = obj-&gt;dates;
+ @dates_list = sort {$b&lt;=&gt;$a} values %DatesHash;
+ $MostRecent = $dates_list[0];
+</PRE>
+<P>
+<PRE> # scalar mode
+ $most_recent = $obj-&gt;dates;
+ print &quot;Most recent date = $most_recent\n&quot;;
+ $most_recent_str = localtime($most_recent);
+ print &quot;Most recent date string = $most_recent_str\n&quot;;
+</PRE>
+<P>
+The <STRONG>symrev</STRONG> method returns the revision against which a specified symbol was defined.
+If the symbol was not defined against any version of this file, 0 is
+returned.
+
+<P>
+<PRE> # gets revision that has 'MY_SYMBOL' defined against it
+ $rev = $obj-&gt;symrev('MY_SYMBOL');
+</PRE>
+<P>
+The <STRONG>daterev</STRONG> method returns revisions which were created before a specified date. Method
+may take one or six arguments. If one arguments is passed, then the
+argument is a date number. If six arguments are passed, then they represent
+a date string.
+
+<P>
+<PRE> # one argument, date number
+ # gets revisions created before Sun Sep 6 22:23:47 1998
+ @revs = $obj-&gt;daterev(841436420);
+</PRE>
+<P>
+<PRE> # six argument
+ # gets revisions created before 25th June 1998 16:45:30
+ @revs = $obj-&gt;daterev(1998, 6, 25, 16, 45, 30);
+</PRE>
+<P>
+The <STRONG>comments</STRONG> method returns a hash of revision comments, keyed on revision. A key value
+of 0 returns the description.
+
+<P>
+<PRE> %comments = $obj-&gt;comments;
+ $description = $comments{0};
+ $comment_1_3 = $comments{'1.3'};
+</PRE>
+<P>
+<HR>
+<H2><A NAME="RCS_SYSTEM_METHODS">RCS SYSTEM METHODS</A></H2>
+<P>
+These methods invoke the RCS system utilities.
+
+<P>
+The <STRONG>ci</STRONG> method calls the RCS ci program.
+
+<P>
+<PRE> # check in, and then check out in unlocked state
+ $obj-&gt;ci('-u');
+</PRE>
+<P>
+The <STRONG>co</STRONG> method calls the RCS co program.
+
+<P>
+<PRE> # check out in locked state
+ $obj-&gt;co('-l');
+</PRE>
+<P>
+The <STRONG>rcs</STRONG> method calls the RCS rcs program.
+
+<P>
+<PRE> # lock file
+ $obj-&gt;rcs('-l');
+</PRE>
+<P>
+The <STRONG>rcsdiff</STRONG> method calls the RCS rcsdiff program. When called in list context, this
+method returns the outpout of the rcsdiff program. When called in scalar
+context, this method returns the return status of the rcsdiff program. The
+return status is 0 for the same, 1 for some differences, and 2 for error
+condition.
+
+<P>
+When called without parameters, rcsdiff does a diff between the current
+working file, and the last revision checked in.
+
+<P>
+<PRE> # call in list context
+ @diff_output = $obj-&gt;rcsdiff;
+</PRE>
+<P>
+<PRE> # call in scalar context
+ $changed = $obj-&gt;rcsdiff;
+ if ($changed) {
+ print &quot;Working file has changed\n&quot;;
+ }
+</PRE>
+<P>
+Call rcsdiff with parameters to do a diff between any two revisions.
+
+<P>
+<PRE> @diff_output = $obj-&gt;rcsdiff('-r1.2', '-r1.1');
+</PRE>
+<P>
+The <STRONG>rlog</STRONG> method calls the RCS rlog program. This method returns the output of the
+rlog program.
+
+<P>
+<PRE> # get complete log output
+ @rlog_complete = $obj-&gt;rlog;
+</PRE>
+<P>
+<PRE> # called with '-h' switch outputs only header information
+ @rlog_header = $obj-&gt;rlog('-h');
+ print @rlog_header;
+</PRE>
+<P>
+The <STRONG>rcsclean</STRONG> method calls the RCS rcsclean program.
+
+<P>
+<PRE> # remove working file
+ $obj-&gt;rcsclean;
+</PRE>
+<P>
+<HR>
+<H1><A NAME="EXAMPLES">EXAMPLES</A></H1>
+<P>
+<HR>
+<H2><A NAME="CREATE_ACCESS_LIST">CREATE ACCESS LIST</A></H2>
+<P>
+Using method <STRONG>rcs</STRONG> with the <STRONG>-a</STRONG> switch allows you to add users to the access list of an RCS archive file.
+
+<P>
+<PRE> use Rcs;
+ $obj = Rcs-&gt;new;
+</PRE>
+<P>
+<PRE> $obj-&gt;rcsdir(&quot;./project_tree/archive&quot;);
+ $obj-&gt;workdir(&quot;./project_tree/src&quot;);
+ $obj-&gt;file(&quot;cornholio.pl&quot;);
+</PRE>
+<P>
+Methos <STRONG>rcs</STRONG> invokes the RCS utility rcs with the same parameters.
+
+<P>
+<PRE> @users = qw(beavis butthead);
+ $obj-&gt;rcs(&quot;-a@users&quot;);
+</PRE>
+<P>
+Calling method <STRONG>access</STRONG> returns list of users on access list.
+
+<P>
+<PRE> $filename = $obj-&gt;file;
+ @access_list = $obj-&gt;access;
+ print &quot;Users @access_list are on the access list of $filename\n&quot;;
+</PRE>
+<P>
+<HR>
+<H2><A NAME="PARSE_RCS_ARCHIVE_FILE">PARSE RCS ARCHIVE FILE</A></H2>
+<P>
+Set class variables and create 'RCS' object. Set bin directory where RCS
+programs (e.g. rcs, ci, co) reside. The default is '/usr/local/bin'. This
+sets the bin directory for all objects.
+
+<P>
+<PRE> use Rcs;
+ Rcs-&gt;bindir('/usr/bin');
+ $obj = Rcs-&gt;new;
+</PRE>
+<P>
+Set information regarding RCS object. This information includes name of the
+working file, directory of working file ('.' by default), and RCS archive
+directory ('./RCS' by default).
+
+<P>
+<PRE> $obj-&gt;rcsdir(&quot;./project_tree/archive&quot;);
+ $obj-&gt;workdir(&quot;./project_tree/src&quot;);
+ $obj-&gt;file(&quot;cornholio.pl&quot;);
+</PRE>
+<P>
+<PRE> $head_rev = $obj-&gt;head;
+ $locker = $obj-&gt;lock;
+ $author = $obj-&gt;author;
+ @access = $obj-&gt;access;
+ @revisions = $obj-&gt;revisions;
+</PRE>
+<P>
+<PRE> $filename = $obj-&gt;file;
+</PRE>
+<P>
+<PRE> if ($locker) {
+ print &quot;Head revision $head_rev is locked by $locker\n&quot;;
+ }
+ else {
+ print &quot;Head revision $head_rev is unlocked\n&quot;;
+ }
+</PRE>
+<P>
+<PRE> if (@access) {
+ print &quot;\nThe following users are on the access list of file $filename\n&quot;;
+ map { print &quot;User: $_\n&quot;} @access;
+ }
+</PRE>
+<P>
+<PRE> print &quot;\nList of all revisions of $filename\n&quot;;
+ foreach $rev (@revisions) {
+ print &quot;Revision: $rev\n&quot;;
+ }
+</PRE>
+<P>
+<HR>
+<H2><A NAME="CHECK_IN_FILE">CHECK-IN FILE</A></H2>
+<P>
+Set class variables and create 'RCS' object. Set bin directory where RCS
+programs (e.g. rcs, ci, co) reside. The default is '/usr/local/bin'. This
+sets the bin directory for all objects.
+
+<P>
+<PRE> use Rcs;
+ Rcs-&gt;bindir('/usr/bin');
+ Rcs-&gt;quiet(0); # turn off quiet mode
+ $obj = Rcs-&gt;new;
+</PRE>
+<P>
+Set information regarding RCS object. This information includes name of
+working file, directory of working file ('.' by default), and RCS archive
+directory ('./RCS' by default).
+
+<P>
+<PRE> $obj-&gt;file('cornholio.pl');
+</PRE>
+<P>
+<PRE> # Set RCS archive directory, is './RCS' by default
+ $obj-&gt;rcsdir(&quot;./project_tree/archive&quot;);
+</PRE>
+<P>
+<PRE> # Set working directory, is '.' by default
+ $obj-&gt;workdir(&quot;./project_tree/src&quot;);
+</PRE>
+<P>
+Check in file using <STRONG>-u</STRONG> switch. This will check in the file, and will then check out the file in an
+unlocked state. The <STRONG>-m</STRONG> switch is used to set the revision comment.
+
+<P>
+Command:
+
+<P>
+<PRE> $obj-&gt;ci('-u', '-mRevision Comment');
+</PRE>
+<P>
+is equivalent to commands:
+
+<P>
+<PRE> $obj-&gt;ci('-mRevision Comment');
+ $obj-&gt;co;
+</PRE>
+<P>
+<HR>
+<H2><A NAME="CHECK_OUT_FILE">CHECK-OUT FILE</A></H2>
+<P>
+Set class variables and create 'RCS' object. Set bin directory where RCS
+programs (e.g. rcs, ci, co) reside. The default is '/usr/local/bin'. This
+sets the bin directory for all objects.
+
+<P>
+<PRE> use Rcs;
+ Rcs-&gt;bindir('/usr/bin');
+ Rcs-&gt;quiet(0); # turn off quiet mode
+ $obj = Rcs-&gt;new;
+</PRE>
+<P>
+Set information regarding RCS object. This information includes name of
+working file, directory of working file ('.' by default), and RCS archive
+directory ('./RCS' by default).
+
+<P>
+<PRE> $obj-&gt;file('cornholio.pl');
+</PRE>
+<P>
+<PRE> # Set RCS archive directory, is './RCS' by default
+ $obj-&gt;rcsdir(&quot;./project_tree/archive&quot;);
+</PRE>
+<P>
+<PRE> # Set working directory, is '.' by default
+ $obj-&gt;workdir(&quot;./project_tree/src&quot;);
+</PRE>
+<P>
+Check out file read-only:
+
+<P>
+<PRE> $obj-&gt;co;
+</PRE>
+<P>
+or check out and lock file:
+
+<P>
+<PRE> $obj-&gt;co('-l');
+</PRE>
+<P>
+<HR>
+<H2><A NAME="RCSDIFF">RCSDIFF</A></H2>
+<P>
+Method <STRONG>rcsdiff</STRONG> does an diff between revisions.
+
+<P>
+<PRE> $obj = Rcs-&gt;new;
+ $obj-&gt;bindir('/usr/bin');
+</PRE>
+<P>
+<PRE> $obj-&gt;rcsdir(&quot;./project_tree/archive&quot;);
+ $obj-&gt;workdir(&quot;./project_tree/src&quot;);
+ $obj-&gt;file(&quot;cornholio.pl&quot;);
+</PRE>
+<P>
+<PRE> print &quot;Diff of current working file\n&quot;;
+ if ($obj-&gt;rcsdiff) { # scalar context
+ print $obj-&gt;rcsdiff; # list context
+ }
+ else {
+ print &quot;Versions are Equal\n&quot;;
+ }
+</PRE>
+<P>
+<PRE> print &quot;\n\nDiff of revisions 1.2 and 1.1\n&quot;;
+ print $obj-&gt;rcsdiff('-r1.2', '-r1.1');
+</PRE>
+<P>
+<HR>
+<H2><A NAME="RCSCLEAN">RCSCLEAN</A></H2>
+<P>
+Method <STRONG>rcsclean</STRONG> will remove an unlocked working file.
+
+<P>
+<PRE> use Rcs;
+ Rcs-&gt;bindir('/usr/bin');
+ Rcs-&gt;quiet(0); # turn off quiet mode
+ $obj = Rcs-&gt;new;
+</PRE>
+<P>
+<PRE> $obj-&gt;rcsdir(&quot;./project_tree/archive&quot;);
+ $obj-&gt;workdir(&quot;./project_tree/src&quot;);
+ $obj-&gt;file(&quot;cornholio.pl&quot;);
+</PRE>
+<P>
+<PRE> print &quot;Quiet mode NOT set\n&quot; unless Rcs-&gt;quiet;
+</PRE>
+<P>
+<PRE> $obj-&gt;rcsclean;
+</PRE>
+<P>
+<HR>
+<H1><A NAME="AUTHOR">AUTHOR</A></H1>
+<P>
+Craig Freter, &lt;<EM>craig@freter.com</EM>&gt;
+
+
+
+<P>
+<HR>
+<H1><A NAME="CONTRIBUTORS">CONTRIBUTORS</A></H1>
+<P>
+David Green, &lt;<EM>greendjf@cvhp152.gpt.co.uk</EM>&gt;
+
+
+
+<P>
+Jamie O'Shaughnessy, &lt;<EM>jamie@thanatar.demon.co.uk</EM>&gt;
+
+
+
+<P>
+<HR>
+<H1><A NAME="COPYRIGHT">COPYRIGHT</A></H1>
+<P>
+Copyright (C) 1997,1998 Craig Freter. All rights reserved. This program is
+free software; you can redistribute it and/or modify it under the same
+terms as Perl itself.
+
+</BODY>
+
+</HTML>
diff --git a/cpan/dist/Rcs/Rcs.pm b/cpan/dist/Rcs/Rcs.pm
new file mode 100644
index 00000000..1c6e2f4b
--- /dev/null
+++ b/cpan/dist/Rcs/Rcs.pm
@@ -0,0 +1,1343 @@
+package Rcs;
+require 5.001;
+use strict;
+use Carp;
+use Time::Local;
+use vars qw($VERSION $revision);
+
+#------------------------------------------------------------------
+# global stuff
+#------------------------------------------------------------------
+$VERSION = '0.09';
+$revision = '$Id: Rcs.pm,v 1.14.1.3 1998/10/19 18:04:48 freter Exp $';
+my $Dir_Sep = ($^O eq 'MSWin32') ? '\\' : '/';
+my $Exe_Ext = ($^O eq 'MSWin32') ? '.exe' : '';
+my $Rcs_Bin_Dir = '/usr/local/bin';
+my $Rcs_Dir = '.' . $Dir_Sep . 'RCS';
+my $Work_Dir = '.';
+my $Quiet = 1; # RCS quiet mode
+my $Arc_Ext = ',v';
+
+#------------------------------------------------------------------
+# RCS object constructor
+#------------------------------------------------------------------
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = {};
+
+ # provide default values for system stuff
+ $self->{"_BINDIR"} = \$Rcs_Bin_Dir;
+ $self->{"_QUIET"} = \$Quiet;
+ $self->{"_RCSDIR"} = \$Rcs_Dir;
+ $self->{"_WORKDIR"} = \$Work_Dir;
+ $self->{"_ARCEXT"} = \$Arc_Ext;
+
+ $self->{FILE} = undef;
+ $self->{ARCFILE} = undef;
+ $self->{AUTHOR} = undef;
+ $self->{COMMENTS} = undef;
+ $self->{DATE} = undef;
+ $self->{LOCK} = undef;
+ $self->{ACCESS} = [];
+ $self->{REVISIONS} = [];
+ $self->{REVINFO} = undef;
+ $self->{STATE} = undef;
+ $self->{SYMBOLS} = undef;
+ bless($self, $class);
+ return $self;
+}
+
+#------------------------------------------------------------------
+# access
+# Access list of archive file.
+#------------------------------------------------------------------
+sub access {
+ my $self = shift;
+
+ if (not @{ $self->{ACCESS} }) {
+ _parse_rcs_header($self);
+ }
+
+ # dereference revisions list
+ my @access = @{ $self->{ACCESS} };
+
+ return @access;
+}
+
+#------------------------------------------------------------------
+# arcext
+# Set the RCS archive file extension (default is ',v').
+#------------------------------------------------------------------
+sub arcext {
+ my $self = shift;
+
+ # called as object method
+ if (ref $self) {
+ if (@_) { ${ $self->{"_ARCEXT"} } = shift };
+ return ${ $self->{"_ARCEXT"} };
+ }
+
+ # called as class method
+ else {
+ if (@_) { $Arc_Ext = shift; }
+ return $Arc_Ext;
+ }
+}
+
+#------------------------------------------------------------------
+# arcfile
+# Name of RCS archive file.
+# If not set then return name of working file with RCS
+# extension (',v').
+#------------------------------------------------------------------
+sub arcfile {
+ my $self = shift;
+ if (@_) { $self->{ARCFILE} = shift }
+ return $self->{ARCFILE} || $self->{FILE} . ${ $self->{"_ARCEXT"} };
+}
+
+#------------------------------------------------------------------
+# author
+# Return the author of an RCS revision.
+# If revision is not provided, default to 'head' revision.
+#------------------------------------------------------------------
+sub author {
+ my $self = shift;
+
+ if (not defined $self->{AUTHOR}) {
+ _parse_rcs_header($self);
+ }
+ my $revision = shift || $self->{HEAD};
+
+ # dereference author hash
+ my %author_array = %{ $self->{AUTHOR} };
+
+ return $author_array{$revision};
+}
+
+#------------------------------------------------------------------
+# bindir
+# Set the bin directory in which the RCS distribution programs
+# reside.
+#------------------------------------------------------------------
+sub bindir {
+ my $self = shift;
+
+ # called as object method
+ if (ref $self) {
+ if (@_) { ${ $self->{"_BINDIR"} } = shift };
+ return ${ $self->{"_BINDIR"} };
+ }
+
+ # called as class method
+ else {
+ if (@_) { $Rcs_Bin_Dir = shift };
+ return $Rcs_Bin_Dir;
+ }
+}
+
+#------------------------------------------------------------------
+# ci
+# Execute RCS 'ci' program.
+# Make archive filename same as working filename unless
+# specifically set.
+#------------------------------------------------------------------
+sub ci {
+ my $self = shift;
+ my @param = @_;
+
+ my $ciprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'ci' . $Exe_Ext;
+ my $rcsdir = ${ $self->{"_RCSDIR"} };
+ my $workdir = ${ $self->{"_WORKDIR"} };
+ my $file = $self->{FILE};
+ my $arcfile = $self->{ARCFILE} || $file;
+
+ my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} };
+ my $workfile = $workdir . $Dir_Sep . $file;
+ push @param, $archive_file, $workfile;
+ unshift @param, "-q" if ${ $self->{"_QUIET"} }; # quiet mode
+
+ # run program
+ croak "ci program $ciprog not found" unless -e $ciprog;
+ croak "ci program $ciprog not executable" unless -x $ciprog;
+ system($ciprog, @param) == 0 or croak "$!";
+
+ # re-parse RCS file and clear comments hash
+ _parse_rcs_header($self);
+ $self->{COMMENTS} = undef;
+}
+
+#------------------------------------------------------------------
+# co
+# Execute RCS 'co' program.
+# Make archive filename same as working filename unless
+# specifically set.
+#------------------------------------------------------------------
+sub co {
+ my $self = shift;
+ my @param = @_;
+
+ my $coprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'co' . $Exe_Ext;
+ my $rcsdir = ${ $self->{"_RCSDIR"} };
+ my $workdir = ${ $self->{"_WORKDIR"} };
+ my $file = $self->{FILE};
+ my $arcfile = $self->{ARCFILE} || $file;
+
+ my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} };
+ my $workfile = $workdir . $Dir_Sep . $file;
+ push @param, $archive_file, $workfile;
+ unshift @param, "-q" if ${ $self->{"_QUIET"} }; # quiet mode
+
+ # run program
+ croak "co program $coprog not found" unless -e $coprog;
+ croak "co program $coprog not executable" unless -x $coprog;
+ system($coprog, @param) == 0 or croak "$!";
+
+ # re-parse RCS file and clear comments hash
+ _parse_rcs_header($self);
+ $self->{COMMENTS} = undef;
+}
+
+#------------------------------------------------------------------
+# comments
+#------------------------------------------------------------------
+sub comments {
+ my $self = shift;
+
+ if (not defined $self->{COMMENTS}) {
+ _parse_rcs_body($self);
+ }
+
+ return %{$self->{COMMENTS}};
+}
+
+#------------------------------------------------------------------
+# daterev
+#
+# Returns revisions which were created before a specified date.
+#
+# Method takes one or six arguments.
+#
+# If one argument, then argument is date number.
+#
+# If six arguments, then year (4 digit year), month (1-12), day
+# of month (1-31), hour (0-23), minute (0-59) and second (0-59).
+#------------------------------------------------------------------
+sub daterev {
+
+ my $self = shift;
+ my $target_time;
+
+ # validate arguments
+ unless (@_ == 1 or @_ == 6) {
+ croak "daterev must have either 1 or 6 arguments";
+ }
+
+ # string date passed
+ if (@_ == 6) {
+ my($year, $mon, $mday, $hour, $min, $sec) = @_;
+
+ if($year !~ /^\d{4}$/) {
+ croak "year (1st param) must be 4 digit number";
+ }
+
+ $mon--; # convert to 0-11 range
+ $target_time = timegm($sec, $min, $hour, $mday, $mon, $year);
+ }
+
+ # system date passed
+ else {
+ $target_time = shift;
+
+ if ($target_time !~ /^\d+$/) {
+ croak "system date must be an integer";
+ }
+ }
+
+ if (not defined $self->{DATE}) {
+ _parse_rcs_header($self);
+ }
+
+ my @revisions = ();
+ my %dates;
+ my %dates_hash = %{$self->{DATE}};
+
+ my $revision;
+ foreach $revision (keys %dates_hash) {
+ my $date = $dates_hash{$revision};
+ $dates{$date}{$revision} = 1;
+ }
+
+ my $date;
+ foreach $date (reverse sort keys %dates) {
+ foreach $revision (keys %{ $dates{$date} }) {
+ push @revisions, $revision if $date <= $target_time;
+ }
+ }
+
+ return wantarray ? @revisions : $revisions[0];
+}
+
+#------------------------------------------------------------------
+# dates
+# Return a hash of revision dates, keyed on revision, when called
+# in list mode.
+# Return the most recent date when called in scalar mode.
+#
+# RCS stores dates in GMT.
+# The date values are system dates.
+#------------------------------------------------------------------
+sub dates {
+ my $self = shift;
+
+ if (not defined $self->{DATE}) {
+ _parse_rcs_header($self);
+ }
+
+ my %DatesHash = %{$self->{DATE}};
+ my @dates_list = sort {$b<=>$a} values %DatesHash;
+ my $MostRecent = $dates_list[0];
+
+ return wantarray ? %DatesHash : $MostRecent;
+}
+
+#------------------------------------------------------------------
+# file
+# Name of working file.
+#------------------------------------------------------------------
+sub file {
+ my $self = shift;
+ if (@_) { $self->{FILE} = shift }
+ return $self->{FILE};
+}
+
+#------------------------------------------------------------------
+# head
+# Return the head revision.
+#------------------------------------------------------------------
+sub head {
+ my $self = shift;
+
+ if (not defined $self->{HEAD}) {
+ _parse_rcs_header($self);
+ }
+ return $self->{HEAD};
+}
+
+#------------------------------------------------------------------
+# lock
+# Return user who has file locked.
+#------------------------------------------------------------------
+sub lock {
+ my $self = shift;
+
+ if (not defined $self->{LOCK}) {
+ _parse_rcs_header($self);
+ }
+ return $self->{LOCK};
+}
+
+#------------------------------------------------------------------
+# quiet
+# Set or un-set RCS quiet mode.
+#------------------------------------------------------------------
+sub quiet {
+ my $self = shift;
+
+ # called as object method
+ if (ref $self) {
+
+ # set/un-set quiet mode
+ if (@_) {
+ my $mode = shift;
+ croak "Passed parameter must be either '0' or '1'"
+ unless $mode == 0 or $mode == 1;
+ ${ $self->{"_QUIET"} } = $mode;
+ return ${ $self->{"_QUIET"} };
+ }
+
+ # access quiet mode
+ else {
+ return ${ $self->{"_QUIET"} };
+ }
+ }
+
+ # called as class method
+ else {
+
+ # set/un-set quiet mode
+ if (@_) {
+ my $mode = shift;
+ croak "Passed parameter must be either '0' or '1'"
+ unless $mode == 0 or $mode == 1;
+ $Quiet = $mode;
+ return $Quiet;
+ }
+
+ # access quiet mode
+ else {
+ return $Quiet;
+ }
+ }
+}
+
+#------------------------------------------------------------------
+# rcs
+# Execute RCS 'rcs' program.
+# Make archive filename same as working filename unless
+# specifically set.
+#------------------------------------------------------------------
+sub rcs {
+ my $self = shift;
+ my @param = @_;
+
+ my $rcsprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'rcs' . $Exe_Ext;
+ my $rcsdir = ${ $self->{"_RCSDIR"} };
+ my $workdir = ${ $self->{"_WORKDIR"} };
+ my $file = $self->{FILE};
+ my $arcfile = $self->{ARCFILE} || $file;
+
+ my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} };
+ my $workfile = $workdir . $Dir_Sep . $file;
+ push @param, $archive_file, $workfile;
+ unshift @param, "-q" if ${ $self->{"_QUIET"} }; # quiet mode
+
+ # run program
+ croak "rcs program $rcsprog not found" unless -e $rcsprog;
+ croak "rcs program $rcsprog not executable" unless -x $rcsprog;
+ system($rcsprog, @param) == 0 or croak "$?";
+
+ # re-parse RCS file and clear comments hash
+ _parse_rcs_header($self);
+ $self->{COMMENTS} = undef;
+}
+
+#------------------------------------------------------------------
+# rcsclean
+# Execute RCS 'rcsclean' program.
+#------------------------------------------------------------------
+sub rcsclean {
+ my $self = shift;
+ my @param = @_;
+
+ my $rcscleanprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'rcsclean' . $Exe_Ext;
+ my $rcsdir = ${ $self->{"_RCSDIR"} };
+ my $workdir = ${ $self->{"_WORKDIR"} };
+ my $file = $self->{FILE};
+ my $arcfile = $self->{ARCFILE} || $file;
+
+ my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} };
+ my $workfile = $workdir . $Dir_Sep . $file;
+ push @param, $archive_file, $workfile;
+
+ # run program
+ croak "rcsclean program $rcscleanprog not found" unless -e $rcscleanprog;
+ croak "rcsclean program $rcscleanprog not executable" unless -x $rcscleanprog;
+ system($rcscleanprog, @param) == 0 or croak "$?";
+
+ # re-parse RCS file and clear comments hash
+ _parse_rcs_header($self);
+ $self->{COMMENTS} = undef;
+}
+
+#------------------------------------------------------------------
+# rcsdiff
+# Execute RCS 'rcsdiff' program.
+# Calling in list context returns the output of rcsdiff, while
+# calling in scalar context returns the return status of the
+# rcsdiff program.
+#------------------------------------------------------------------
+sub rcsdiff {
+ my $self = shift;
+ my @param = @_;
+
+ my $rcsdiff_prog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'rcsdiff' . $Exe_Ext;
+ my $rcsdir = ${ $self->{"_RCSDIR"} };
+ my $arcfile = $self->{ARCFILE} || $self->{FILE};
+ $arcfile = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} };
+ my $workfile = $self->workdir . $Dir_Sep . $self->file;
+
+ # un-taint parameter string
+ unshift @param, "-q" if ${ $self->{"_QUIET"} }; # quiet mode
+ my $param_str = join(' ', @param);
+ $param_str =~ s/([\w-]+)/$1/g;
+
+ croak "rcsdiff program $rcsdiff_prog not found" unless -e $rcsdiff_prog;
+ croak "rcsdiff program $rcsdiff_prog not executable" unless -x $rcsdiff_prog;
+ open(DIFF, "$rcsdiff_prog $param_str $arcfile $workfile |");
+ my @diff_output = <DIFF>;
+
+ # rcsdiff returns exit status 0 for no differences, 1 for differences,
+ # and 2 for error condition.
+ close DIFF;
+ my $status = $?;
+ croak "$rcsdiff_prog failed" if $status == 2;
+ return wantarray ? @diff_output : $status;
+}
+
+#------------------------------------------------------------------
+# rcsdir
+# Location of 'RCS' archive directory.
+#------------------------------------------------------------------
+sub rcsdir {
+ my $self = shift;
+
+ # called as object method
+ if (ref $self) {
+ if (@_) { ${ $self->{"_RCSDIR"} } = shift }
+ return ${ $self->{"_RCSDIR"} };
+ }
+
+ # called as class method
+ else {
+ if (@_) { $Rcs_Dir = shift }
+ return $Rcs_Dir;
+ }
+}
+
+#------------------------------------------------------------------
+# revdate
+# Return the revision date of an RCS revision.
+# If revision is not provided, default to 'head' revision.
+#
+# RCS stores dates in GMT. This method will return dates relative
+# to the local time zone.
+#------------------------------------------------------------------
+sub revdate {
+ my $self = shift;
+
+ if (not defined $self->{DATE}) {
+ _parse_rcs_header($self);
+ }
+ my $revision = shift || $self->{HEAD};
+
+ # dereference date hash
+ my %date_array = %{ $self->{DATE} };
+ my $date_str = $date_array{$revision};
+
+ return wantarray ? localtime($date_str) : $date_str;
+}
+
+#------------------------------------------------------------------
+# revisions
+#------------------------------------------------------------------
+sub revisions {
+ my $self = shift;
+
+ if (not @{ $self->{REVISIONS} }) {
+ _parse_rcs_header($self);
+ }
+
+ # dereference revisions list
+ my @revisions = @{ $self->{REVISIONS} };
+
+ @revisions;
+}
+
+#------------------------------------------------------------------
+# rlog
+# Execute RCS 'rlog' program.
+# Make archive filename same as working filename unless
+# specifically set.
+#------------------------------------------------------------------
+sub rlog {
+ my $self = shift;
+ my @param = @_;
+
+ my $rlogprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'rlog' . $Exe_Ext;
+ my $rcsdir = ${ $self->{"_RCSDIR"} };
+ my $arcfile = $self->{ARCFILE} || $self->{FILE};
+
+ # un-taint parameter string
+ my $param_str = join(' ', @param);
+ $param_str =~ s/([\w-]+)/$1/g;
+
+ my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} };
+ croak "rlog program $rlogprog not found" unless -e $rlogprog;
+ croak "rlog program $rlogprog not executable" unless -x $rlogprog;
+ open(RLOG, "$rlogprog $param_str $archive_file |");
+
+ my @logoutput = <RLOG>;
+ close RLOG;
+ croak "$rlogprog failed" if $?;
+ @logoutput;
+}
+
+#------------------------------------------------------------------
+# state
+# If revision is not provided, default to 'head' revision
+#------------------------------------------------------------------
+sub state {
+ my $self = shift;
+
+ if (not defined $self->{STATE}) {
+ _parse_rcs_header($self);
+ }
+ my $revision = shift || $self->{HEAD};
+
+ # dereference author hash
+ my %state_array = %{ $self->{STATE} };
+
+ return $state_array{$revision};
+}
+
+#------------------------------------------------------------------
+# symbol
+# If revision is not provided, default to 'head' revision
+#------------------------------------------------------------------
+sub symbol {
+ my $self = shift;
+
+ if (not defined $self->{SYMBOLS}) {
+ _parse_rcs_header($self);
+ }
+ my $revision = shift || $self->{HEAD};
+
+ # dereference symbols hash
+ my %sym_array = %{ $self->{SYMBOLS} };
+
+ return '' if not defined $sym_array{$revision};
+
+ my @symbols = @{ $sym_array{$revision} };
+
+ # return only first array element if user wants scalar
+ return wantarray ? @symbols : $symbols[0];
+}
+
+#------------------------------------------------------------------
+# symbols
+# Returns hash of all revisions keyed on symbol defined against file.
+#------------------------------------------------------------------
+sub symbols {
+ my $self = shift;
+
+ if(not defined $self->{SYMBOLS}) {
+ _parse_rcs_header($self);
+ }
+
+ my %symbols;
+
+ # loop through each revision
+ my $rev;
+ foreach $rev (@{ $self->{REVISIONS} }) {
+ my $sym;
+ foreach $sym (@{ $self->{SYMBOLS}->{$rev} }) {
+ $symbols{$sym} = $rev;
+ }
+ }
+ return %symbols;
+}
+
+#------------------------------------------------------------------
+# symrev
+# Returns the revision against which a specified symbol was
+# defined. If the symbol was not defined against any version
+# of this file, 0 is returned.
+#------------------------------------------------------------------
+sub symrev {
+ my $self = shift;
+ my $sym = shift;
+ if(! defined $sym) {
+ croak "You must supply a symbol to symrev";
+ }
+
+ if (not defined $self->{SYMBOLS}) {
+ _parse_rcs_header($self);
+ }
+
+ my $ret_rev = 0;
+ my %symbols;
+
+ # loop through each revision
+ my $rev;
+ REV_LOOP:
+ foreach $rev (@{ $self->{REVISIONS} }) {
+ # loop through each symbol defined against
+ # this revision
+ my $s;
+ foreach $s (@{ $self->{SYMBOLS}->{$rev} }) {
+
+ # store each revision matching the pattern
+ if (wantarray) {
+ $symbols{$s} = $rev if $s =~ /$sym/;
+ }
+
+ # if it's the one we're looking for, we can
+ # quit as we've found the revision we want
+ else {
+ if($s eq $sym) {
+ $ret_rev = $rev;
+ last REV_LOOP;
+ }
+ }
+ }
+ }
+
+ return wantarray ? %symbols : $ret_rev;
+}
+
+#------------------------------------------------------------------
+# workdir
+# Location of working directory.
+#------------------------------------------------------------------
+sub workdir {
+ my $self = shift;
+
+ # called as object method
+ if (ref $self) {
+ if (@_) { ${ $self->{"_WORKDIR"} } = shift }
+ return ${ $self->{"_WORKDIR"} };
+ }
+
+ # called as class method
+ else {
+ if (@_) { $Work_Dir = shift }
+ return $Work_Dir;
+ }
+}
+
+#------------------------------------------------------------------
+# _parse_rcs_body
+# Private function
+#------------------------------------------------------------------
+sub _parse_rcs_body {
+
+ my $self = shift;
+ local $_;
+
+ my %comments;
+
+ my $rcsdir = ${ $self->{"_RCSDIR"} };
+ my $file = $self->{FILE};
+ my $rcs_file = $rcsdir . $Dir_Sep . $file . ${ $self->{"_ARCEXT"} };
+
+ # parse RCS archive file
+ open RCS_FILE, $rcs_file or croak "Unable to open $rcs_file";
+
+ # skip header info and get description
+ DESC: while (<RCS_FILE>) {
+ if (/^desc$/) {
+ $comments{0} = '';
+ $_ = <RCS_FILE>; # read first line
+ s/^\@//; # remove leading '@'
+ while (1) {
+ last DESC if /^\@$/;
+ s/\@\@/\@/g; # RCS replaces single '@' with '@@'
+ $comments{0} .= $_;
+ $_ = <RCS_FILE>;
+ }
+ }
+ }
+
+ # parse revision comments
+ my $revision;
+ REVISION: while (<RCS_FILE>) {
+ if (/^[\d\.]+$/) {
+ chomp($revision = $_);
+ $_ = <RCS_FILE>;
+ if (/^log$/) {
+ $comments{$revision} = '';
+ $_ = <RCS_FILE>; # read first line
+ s/^\@//; # remove leading '@'
+ while (1) {
+ next REVISION if /^\@$/;
+ s/\@\@/\@/g; # RCS replaces single '@' with '@@'
+ $comments{$revision} .= $_;
+ $_ = <RCS_FILE>;
+ }
+ }
+ }
+ }
+
+ # loop through 'text' section to avoid capturing bogus info
+ continue {
+ if (/^text$/) { # 'text' tag should always be there, but check anyway
+ while (<RCS_FILE>) {
+ s/\@\@//g; # RCS replaces single '@' with '@@'
+ last if /\@$/
+ }
+ }
+ }
+
+ close RCS_FILE;
+ $self->{COMMENTS} = \%comments;
+}
+
+#------------------------------------------------------------------
+# _parse_rcs_header
+# Private function
+# Directly parse the RCS archive file.
+#------------------------------------------------------------------
+sub _parse_rcs_header {
+
+ my $self = shift;
+ local $_;
+
+ my ($head, $lock);
+ my (@access_list, @revisions);
+ my (%author, %date, %state, %symbols);
+
+ my $rcsdir = ${ $self->{"_RCSDIR"} };
+ my $file = $self->{FILE};
+ my $rcs_file = $rcsdir . $Dir_Sep . $file . ${ $self->{"_ARCEXT"} };
+
+ # parse RCS archive file
+ open RCS_FILE, $rcs_file
+ or croak "Unable to open $rcs_file";
+ while (<RCS_FILE>) {
+ next if /^\s*$/; # skip blank lines
+ last if /^desc$/; # end of header info
+
+ # get head revision
+ if (/^head\s/) {
+ ($head) = /^head\s+(.*?);$/;
+ next;
+ }
+
+ # get access list
+ if (/^access$/) {
+ while (<RCS_FILE>) {
+ chomp;
+ s/\s//g; # remove all whitespace
+ push @access_list, (split(/;/))[0];
+ last if /;$/;
+ }
+ next;
+ }
+
+ # get locker
+ # get symbols
+ if (/^symbols$/) {
+ while (<RCS_FILE>) {
+ chomp;
+ s/\s//g; # remove all whitespace
+ my ($sym, $rev) = split(/:/);
+ $rev =~ s/;$//;
+ push @{ $symbols{$rev} }, $sym;
+ last if /;$/;
+ }
+ next;
+ }
+
+ # get locker
+ if (/^locks/) {
+
+ # file not locked
+ if (/strict/) {
+ $lock = '';
+ next;
+ }
+
+ # get user who has file locked
+ my $next_line = <RCS_FILE>; # read next line
+ ($lock) = $next_line =~ m/^\s*(\w+):/;
+ next;
+ }
+
+ # get all revisions
+ if (/^\d+\.\d+/) {
+ chomp;
+ push @revisions, $_;
+
+ # get author, state and date of each revision
+ my $next_line = <RCS_FILE>;
+ chop(my $author = (split(/\s+/, $next_line))[3]);
+ chop(my $state = (split(/\s+/, $next_line))[5]);
+ chop(my $date = (split(/\s+/, $next_line))[1]);
+
+ # store date as date number
+ my ($year, $mon, $mday, $hour, $min, $sec) = split(/\./, $date);
+ $mon--; # convert to 0-11 range
+ my @date = ($sec,$min,$hour,$mday,$mon,$year);
+
+ # store value in hash using revision as key
+ $author{$_} = $author;
+ $state{$_} = $state;
+ $date{$_} = timegm(@date);
+ }
+ }
+ close RCS_FILE;
+
+ $self->{HEAD} = $head;
+ $self->{LOCK} = $lock;
+ $self->{ACCESS} = \@access_list;
+ $self->{REVISIONS} = \@revisions;
+ $self->{AUTHOR} = \%author;
+ $self->{DATE} = \%date;
+ $self->{STATE} = \%state;
+ $self->{SYMBOLS} = \%symbols;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Rcs - Perl Object Class for Revision Control System (RCS).
+
+=head1 SYNOPSIS
+
+ use Rcs;
+
+=head1 DESCRIPTION
+
+This Perl module provides an object oriented interface to access
+B<Revision Control System (RCS)> utilities. RCS must be installed on
+the system prior to using this module. This module should simplify
+the creation of an RCS front-end.
+
+=head2 OBJECT CONSTRUCTOR
+
+The B<new> method may be used as either a class method or an object
+method to create a new object.
+
+ # called as class method
+ $obj = Rcs->new;
+
+ # called as object method
+ $newobj = $obj->new;
+
+=head2 CLASS METHODS
+
+Besides the object constructor, there are three class methods provided
+which effect any newly created objects.
+
+The B<arcext> method sets the RCS archive extension, which is ',v' by
+default.
+
+ # set/unset RCS archive extension
+ Rcs->arcext(''); # set no archive extension
+ Rcs->arcext(',v'); # set archive extension to ',v'
+ $arc_ext = Rcs->arcext(); # get current archive extension
+
+The B<bindir> method sets the directory path where the RCS executables
+(i.e. rcs, ci, co) are located. The default location is '/usr/local/bin'.
+
+ # set RCS bin directory
+ Rcs->bindir('/usr/bin');
+
+ # access RCS bin directory
+ $bin_dir = Rcs->bindir;
+
+The B<quiet> method sets/unsets the quiet mode for the RCS executables.
+Quiet mode is set by default.
+
+ # set/unset RCS quiet mode
+ Rcs->quiet(0); # unset quiet mode
+ Rcs->quiet(1); # set quiet mode
+
+ # access RCS quiet mode
+ $quiet_mode = Rcs->quiet;
+
+These methods may also be called as object methods.
+
+ $obj->arcext('');
+ $obj->bindir('/usr/bin');
+ $obj->quiet(0);
+
+=head2 OBJECT ATTRIBUTE METHODS
+
+These methods set the attributes of the RCS object.
+
+The B<file> method is used to set the name of the RCS working file. The
+filename must be set before invoking any access of modifier methods on the
+object.
+
+ $obj->file('mr_anderson.pl');
+
+The B<arcfile> method is used to set the name of the RCS archive file.
+Using this method is optional, as the other methods will assume the archive
+filename is the same as the working file unless specified otherwise. The
+RCS archive extension (default ',v') is automatically added to the filename.
+
+ $obj->arcfile('principle_mcvicker.pl');
+
+The B<workdir> methods set the path of the RCS working directory. If not
+specified, default path is '.' (current working directory).
+
+ $obj->workdir('/usr/local/source');
+
+The B<rcsdir> methods set the path of the RCS archive directory. If not
+specified, default path is './RCS'.
+
+ $obj->rcsdir('/usr/local/archive');
+
+=head2 RCS PARSE METHODS
+
+This class provides methods to directly parse the RCS archive file.
+
+The B<access> method returns a list of all user on the access list.
+
+ @access_list = $obj->access;
+
+The B<author> method returns the author of the revision. The head revision
+is used if no revision argument is passed to method.
+
+ # returns the author of revision '1.3'
+ $author = $obj->author('1.3');
+
+ # returns the authos of the head revision
+ $author = $obj->author;
+
+The B<head> method returns the head revision.
+
+ $head = $obj->head;
+
+The B<lock> method returns the locker of the revision. The method returns
+null if the revision is unlocked. The head revision is used if no revision
+argument is passed to method.
+
+ # returns locker of revision '1.3'
+ $locker = $obj->lock('1.3');
+
+ # returns locker of head revision
+ $locker = $obj->lock;
+
+The B<revisions> method returns a list of all revisions of archive file.
+
+ @revisions = $obj->revisions;
+
+The B<state> method returns the state of the revision. The head revision
+is used if no revision argument is passed to method.
+
+ # returns state of revision '1.3'
+ $state = $obj->state('1.3');
+
+ # returns state of head revision
+ $state = $obj->state;
+
+The B<symbol> method returns the symbol(s) associated with a revision.
+If called in list context, method returns all symbols associated with
+revision. If called in scalar context, method returns last symbol
+assciated with a revision. The head revision is used if no revision argument
+is passed to method.
+
+ # list context, returns all symbols associated with revision 1.3
+ @symbols = $obj->symbol('1.3');
+
+ # list context, returns all symbols associated with head revision
+ @symbols = $obj->symbol;
+
+ # scalar context, returns last symbol associated with revision 1.3
+ $symbol = $obj->symbol('1.3');
+
+ # scalar context, returns last symbol associated with head revision
+ $symbol = $obj->symbol;
+
+The B<symbols> method returns a hash, keyed by symbol, of all of the revisions
+associated with the file.
+
+ %symbols = $obj->symbols;
+ foreach $sym (keys %symbols) {
+ $rev = $symbols{$sym};
+ }
+
+The B<revdate> method returns the date of a revision. The returned date format
+is the same as the localtime format. When called as a scalar, it returns the
+system date number. If called is list context, the list
+($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) is returned.
+
+ # scalar mode
+ $scalar_date = $obj->revdate;
+ print "Scalar date number = $scalar_date\n";
+ $date_str = localtime($scalar_date);
+ print "Scalar date string = $date_str\n";
+
+ # list mode
+ @list_date = $obj->revdate;
+ print "List date = @list_date\n";
+
+The B<dates> method returns a hash of revision dates, keyed on revision. The
+hash values are system date numbers. When called in scalar mode, the method
+returns the most recent revision date.
+
+ # list mode
+ %DatesHash = obj->dates;
+ @dates_list = sort {$b<=>$a} values %DatesHash;
+ $MostRecent = $dates_list[0];
+
+ # scalar mode
+ $most_recent = $obj->dates;
+ print "Most recent date = $most_recent\n";
+ $most_recent_str = localtime($most_recent);
+ print "Most recent date string = $most_recent_str\n";
+
+The B<symrev> method returns the revision against which a specified symbol was
+defined. If the symbol was not defined against any version of this file, 0 is
+returned.
+
+ # gets revision that has 'MY_SYMBOL' defined against it
+ $rev = $obj->symrev('MY_SYMBOL');
+
+The B<daterev> method returns revisions which were created before a specified
+date. Method may take one or six arguments. If one arguments is passed, then
+the argument is a date number. If six arguments are passed, then they represent
+a date string.
+
+ # one argument, date number
+ # gets revisions created before Sun Sep 6 22:23:47 1998
+ @revs = $obj->daterev(841436420);
+
+ # six argument
+ # gets revisions created before 25th June 1998 16:45:30
+ @revs = $obj->daterev(1998, 6, 25, 16, 45, 30);
+
+The B<comments> method returns a hash of revision comments, keyed on revision.
+A key value of 0 returns the description.
+
+ %comments = $obj->comments;
+ $description = $comments{0};
+ $comment_1_3 = $comments{'1.3'};
+
+=head2 RCS SYSTEM METHODS
+
+These methods invoke the RCS system utilities.
+
+The B<ci> method calls the RCS ci program.
+
+ # check in, and then check out in unlocked state
+ $obj->ci('-u');
+
+The B<co> method calls the RCS co program.
+
+ # check out in locked state
+ $obj->co('-l');
+
+The B<rcs> method calls the RCS rcs program.
+
+ # lock file
+ $obj->rcs('-l');
+
+The B<rcsdiff> method calls the RCS rcsdiff program. When called in
+list context, this method returns the outpout of the rcsdiff program.
+When called in scalar context, this method returns the return status of
+the rcsdiff program. The return status is 0 for the same, 1 for some
+differences, and 2 for error condition.
+
+When called without parameters, rcsdiff does a diff between the current
+working file, and the last revision checked in.
+
+ # call in list context
+ @diff_output = $obj->rcsdiff;
+
+ # call in scalar context
+ $changed = $obj->rcsdiff;
+ if ($changed) {
+ print "Working file has changed\n";
+ }
+
+Call rcsdiff with parameters to do a diff between any two revisions.
+
+ @diff_output = $obj->rcsdiff('-r1.2', '-r1.1');
+
+The B<rlog> method calls the RCS rlog program. This method returns the
+output of the rlog program.
+
+ # get complete log output
+ @rlog_complete = $obj->rlog;
+
+ # called with '-h' switch outputs only header information
+ @rlog_header = $obj->rlog('-h');
+ print @rlog_header;
+
+The B<rcsclean> method calls the RCS rcsclean program.
+
+ # remove working file
+ $obj->rcsclean;
+
+
+=head1 EXAMPLES
+
+=head2 CREATE ACCESS LIST
+
+Using method B<rcs> with the B<-a> switch allows you to add users to
+the access list of an RCS archive file.
+
+ use Rcs;
+ $obj = Rcs->new;
+
+ $obj->rcsdir("./project_tree/archive");
+ $obj->workdir("./project_tree/src");
+ $obj->file("cornholio.pl");
+
+Methos B<rcs> invokes the RCS utility rcs with the same parameters.
+
+ @users = qw(beavis butthead);
+ $obj->rcs("-a@users");
+
+Calling method B<access> returns list of users on access list.
+
+ $filename = $obj->file;
+ @access_list = $obj->access;
+ print "Users @access_list are on the access list of $filename\n";
+
+
+=head2 PARSE RCS ARCHIVE FILE
+
+Set class variables and create 'RCS' object.
+Set bin directory where RCS programs (e.g. rcs, ci, co) reside. The
+default is '/usr/local/bin'. This sets the bin directory for all objects.
+
+ use Rcs;
+ Rcs->bindir('/usr/bin');
+ $obj = Rcs->new;
+
+Set information regarding RCS object. This information includes name of the
+working file, directory of working file ('.' by default), and RCS archive
+directory ('./RCS' by default).
+
+ $obj->rcsdir("./project_tree/archive");
+ $obj->workdir("./project_tree/src");
+ $obj->file("cornholio.pl");
+
+ $head_rev = $obj->head;
+ $locker = $obj->lock;
+ $author = $obj->author;
+ @access = $obj->access;
+ @revisions = $obj->revisions;
+
+ $filename = $obj->file;
+
+ if ($locker) {
+ print "Head revision $head_rev is locked by $locker\n";
+ }
+ else {
+ print "Head revision $head_rev is unlocked\n";
+ }
+
+ if (@access) {
+ print "\nThe following users are on the access list of file $filename\n";
+ map { print "User: $_\n"} @access;
+ }
+
+ print "\nList of all revisions of $filename\n";
+ foreach $rev (@revisions) {
+ print "Revision: $rev\n";
+ }
+
+=head2 CHECK-IN FILE
+
+Set class variables and create 'RCS' object.
+Set bin directory where RCS programs (e.g. rcs, ci, co) reside. The
+default is '/usr/local/bin'. This sets the bin directory for all objects.
+
+ use Rcs;
+ Rcs->bindir('/usr/bin');
+ Rcs->quiet(0); # turn off quiet mode
+ $obj = Rcs->new;
+
+Set information regarding RCS object. This information includes name of
+working file, directory of working file ('.' by default), and RCS archive
+directory ('./RCS' by default).
+
+ $obj->file('cornholio.pl');
+
+ # Set RCS archive directory, is './RCS' by default
+ $obj->rcsdir("./project_tree/archive");
+
+ # Set working directory, is '.' by default
+ $obj->workdir("./project_tree/src");
+
+Check in file using B<-u> switch. This will check in the file, and will then
+check out the file in an unlocked state. The B<-m> switch is used to set the
+revision comment.
+
+Command:
+
+ $obj->ci('-u', '-mRevision Comment');
+
+is equivalent to commands:
+
+ $obj->ci('-mRevision Comment');
+ $obj->co;
+
+=head2 CHECK-OUT FILE
+
+Set class variables and create 'RCS' object.
+Set bin directory where RCS programs (e.g. rcs, ci, co) reside. The
+default is '/usr/local/bin'. This sets the bin directory for all objects.
+
+ use Rcs;
+ Rcs->bindir('/usr/bin');
+ Rcs->quiet(0); # turn off quiet mode
+ $obj = Rcs->new;
+
+Set information regarding RCS object. This information includes name of
+working file, directory of working file ('.' by default), and RCS archive
+directory ('./RCS' by default).
+
+ $obj->file('cornholio.pl');
+
+ # Set RCS archive directory, is './RCS' by default
+ $obj->rcsdir("./project_tree/archive");
+
+ # Set working directory, is '.' by default
+ $obj->workdir("./project_tree/src");
+
+Check out file read-only:
+
+ $obj->co;
+
+or check out and lock file:
+
+ $obj->co('-l');
+
+=head2 RCSDIFF
+
+Method B<rcsdiff> does an diff between revisions.
+
+ $obj = Rcs->new;
+ $obj->bindir('/usr/bin');
+
+ $obj->rcsdir("./project_tree/archive");
+ $obj->workdir("./project_tree/src");
+ $obj->file("cornholio.pl");
+
+ print "Diff of current working file\n";
+ if ($obj->rcsdiff) { # scalar context
+ print $obj->rcsdiff; # list context
+ }
+ else {
+ print "Versions are Equal\n";
+ }
+
+ print "\n\nDiff of revisions 1.2 and 1.1\n";
+ print $obj->rcsdiff('-r1.2', '-r1.1');
+
+=head2 RCSCLEAN
+
+Method B<rcsclean> will remove an unlocked working file.
+
+ use Rcs;
+ Rcs->bindir('/usr/bin');
+ Rcs->quiet(0); # turn off quiet mode
+ $obj = Rcs->new;
+
+ $obj->rcsdir("./project_tree/archive");
+ $obj->workdir("./project_tree/src");
+ $obj->file("cornholio.pl");
+
+ print "Quiet mode NOT set\n" unless Rcs->quiet;
+
+ $obj->rcsclean;
+
+=head1 AUTHOR
+
+Craig Freter, E<lt>F<craig@freter.com>E<gt>
+
+=head1 CONTRIBUTORS
+
+David Green, E<lt>F<greendjf@cvhp152.gpt.co.uk>E<gt>
+
+Jamie O'Shaughnessy, E<lt>F<jamie@thanatar.demon.co.uk>E<gt>
+
+=head1 COPYRIGHT
+
+Copyright (C) 1997,1998 Craig Freter. All rights reserved.
+This program is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
+
+=cut
+
diff --git a/cpan/dist/Rcs/examples/access.pl b/cpan/dist/Rcs/examples/access.pl
new file mode 100755
index 00000000..96597e5c
--- /dev/null
+++ b/cpan/dist/Rcs/examples/access.pl
@@ -0,0 +1,21 @@
+#!/usr/local/bin/perl -w
+#------------------------------------------
+# Add users to access list.
+#------------------------------------------
+use strict;
+use Rcs;
+
+Rcs->bindir('/usr/bin');
+Rcs->quiet(0);
+my $obj = Rcs->new;
+
+$obj->rcsdir("./project/RCS");
+$obj->workdir("./project/src");
+$obj->file("testfile");
+
+my @users = qw(beavis butthead);
+$obj->rcs("-a@users");
+
+my $filename = $obj->file;
+my @access_list = $obj->access;
+print "Users @access_list are on the access list of $filename\n";
diff --git a/cpan/dist/Rcs/examples/ci-tree.pl b/cpan/dist/Rcs/examples/ci-tree.pl
new file mode 100755
index 00000000..4917ba1a
--- /dev/null
+++ b/cpan/dist/Rcs/examples/ci-tree.pl
@@ -0,0 +1,46 @@
+#!/usr/bin/perl -w
+
+use strict;
+use File::Find;
+use File::Path;
+use Rcs;
+
+Rcs->bindir("/usr/bin");
+
+my $comment = shift;
+
+# Traverse desired filesystems
+
+my $tree_root = '/home/freter/tmp';
+my $rcs_path = '/RCS';
+my $src_path = '/src';
+
+find(\&wanted, $tree_root . $src_path);
+
+exit;
+
+sub wanted {
+ my $relative_path = $File::Find::dir;
+ ($relative_path) =~ s{^$tree_root$src_path}{};
+ print $relative_path;
+ print "\n";
+ mkpath([$tree_root . $rcs_path . $relative_path], 1, 0755);
+
+ return unless -f;
+ my $obj = Rcs->new;
+ $obj->file($_);
+ $obj->rcsdir($tree_root . $rcs_path . $relative_path);
+ $obj->workdir($tree_root . $src_path . $relative_path);
+
+ # archive file exists
+ if (! -e $obj->rcsdir . '/' . $obj->arcfile) {
+ print "Initial Check-in\n";
+ $obj->ci("-l", "-t-$comment");
+ }
+
+ # create archive file
+ else {
+ print "Check-in\n";
+ $obj->ci("-l", "-m$comment");
+ }
+}
diff --git a/cpan/dist/Rcs/examples/ci.pl b/cpan/dist/Rcs/examples/ci.pl
new file mode 100755
index 00000000..7d1ae42f
--- /dev/null
+++ b/cpan/dist/Rcs/examples/ci.pl
@@ -0,0 +1,28 @@
+#!/usr/local/bin/perl -w
+#------------------------------------------
+# Check-in source file.
+#------------------------------------------
+use strict;
+use Rcs;
+
+Rcs->bindir('/usr/bin');
+Rcs->quiet(0); # turn off quiet mode
+my $obj = Rcs->new;
+
+print "Quiet mode set\n" if Rcs->quiet;
+
+$obj->rcsdir("./project/RCS");
+$obj->workdir("./project/src");
+$obj->file("testfile");
+
+# archive file exists
+if (! -e $obj->rcsdir . '/' . $obj->arcfile) {
+ print "Initial Check-in\n";
+ $obj->ci("-u");
+}
+
+# create archive file
+else {
+ print "Check-in\n";
+ $obj->ci("-l");
+}
diff --git a/cpan/dist/Rcs/examples/co-tree.pl b/cpan/dist/Rcs/examples/co-tree.pl
new file mode 100755
index 00000000..bf35b4f2
--- /dev/null
+++ b/cpan/dist/Rcs/examples/co-tree.pl
@@ -0,0 +1,45 @@
+#!/usr/bin/perl -w
+
+use strict;
+use File::Find;
+use File::Path;
+use Rcs;
+
+Rcs->bindir("/usr/bin");
+
+my $lock = 0;
+
+# Traverse desired filesystems
+
+my $tree_root = '/home/freter/tmp';
+my $rcs_path = '/RCS';
+my $chkpt_path = '/chkpt';
+
+find(\&wanted, $tree_root . $rcs_path);
+
+exit;
+
+sub wanted {
+ my $relative_path = $File::Find::dir;
+ ($relative_path) =~ s{^$tree_root$rcs_path}{};
+ print $relative_path;
+ print "\n";
+ mkpath([$tree_root . $chkpt_path . $relative_path], 1, 0755);
+
+ return unless -f;
+ my $obj = Rcs->new;
+ s/,v$//;
+ $obj->file($_);
+ $obj->rcsdir($tree_root . $rcs_path . $relative_path);
+ $obj->workdir($tree_root . $chkpt_path . $relative_path);
+
+ # check out and lock
+ if ($lock) {
+ $obj->co("-l");
+ }
+
+ # check out read only
+ else {
+ $obj->co;
+ }
+}
diff --git a/cpan/dist/Rcs/examples/co.pl b/cpan/dist/Rcs/examples/co.pl
new file mode 100755
index 00000000..de0208db
--- /dev/null
+++ b/cpan/dist/Rcs/examples/co.pl
@@ -0,0 +1,21 @@
+#!/usr/local/bin/perl -w
+#------------------------------------------
+# Check-out source file.
+#------------------------------------------
+use strict;
+use Rcs;
+
+Rcs->bindir('/usr/bin');
+Rcs->quiet(0); # turn off quiet mode
+my $obj = Rcs->new;
+
+print "Quiet mode set\n" if Rcs->quiet;
+
+$obj->rcsdir("./project/RCS");
+$obj->workdir("./project/src");
+$obj->file("testfile");
+my $revision = shift || $obj->head;
+die "Revision $revision does not exist\n"
+ unless grep /^$revision$/, $obj->revisions;
+
+$obj->co("-l${revision}");
diff --git a/cpan/dist/Rcs/examples/comments.pl b/cpan/dist/Rcs/examples/comments.pl
new file mode 100755
index 00000000..55b0c055
--- /dev/null
+++ b/cpan/dist/Rcs/examples/comments.pl
@@ -0,0 +1,24 @@
+#!/usr/local/bin/perl -w
+#------------------------------------------
+# Access comments hash
+#------------------------------------------
+use strict;
+use lib '.';
+use Rcs;
+
+Rcs->bindir('/usr/bin');
+my $obj = Rcs->new;
+
+$obj->rcsdir("./project/RCS");
+$obj->workdir("./project/src");
+$obj->file("testfile");
+
+my %comments = $obj->comments;
+my $revision;
+foreach $revision (keys %comments) {
+ #my $comments = $comments{$revision} ? $comments{$revision} : 'GGG';
+ my $comments = $comments{$revision};
+ print "======\n";
+ print "Revision: $revision\n";
+ print "$comments###\n";
+}
diff --git a/cpan/dist/Rcs/examples/comments2.pl b/cpan/dist/Rcs/examples/comments2.pl
new file mode 100755
index 00000000..9a2b7dae
--- /dev/null
+++ b/cpan/dist/Rcs/examples/comments2.pl
@@ -0,0 +1,24 @@
+#!/usr/local/bin/perl -w
+#------------------------------------------
+# Access comments hash
+#------------------------------------------
+use strict;
+use lib '.';
+use Rcs;
+
+Rcs->bindir('/usr/bin');
+my $obj = Rcs->new;
+
+$obj->rcsdir("./project/RCS");
+$obj->workdir("./project/src");
+$obj->file("Rcs.pm");
+
+my %comments = $obj->comments;
+my $revision;
+foreach $revision (sort keys %comments) {
+ #my $comments = $comments{$revision} ? $comments{$revision} : 'GGG';
+ my $comments = $comments{$revision};
+ print "======\n";
+ print "Revision: $revision\n";
+ print "$comments###\n";
+}
diff --git a/cpan/dist/Rcs/examples/daterev.pl b/cpan/dist/Rcs/examples/daterev.pl
new file mode 100755
index 00000000..f6b7c6ac
--- /dev/null
+++ b/cpan/dist/Rcs/examples/daterev.pl
@@ -0,0 +1,53 @@
+#!/usr/local/bin/perl -w
+#------------------------------------------
+# Test daterev method
+#------------------------------------------
+use strict;
+use Time::Local;
+use lib '.';
+use Rcs;
+
+Rcs->bindir('/usr/bin');
+my $obj = Rcs->new;
+
+$obj->rcsdir("./project/RCS");
+$obj->workdir("./project/src");
+$obj->file("testfile");
+
+my @date_array = @ARGV;
+my($year, $mon, $mday, $hour, $min, $sec) = @date_array;
+$mon--; # convert to 0-11 range
+my $target_time = timegm($sec, $min, $hour, $mday, $mon, $year);
+
+print "Called as 6 argument method\n";
+# scalar mode
+my $revision = $obj->daterev(@date_array);
+my $date_str = gmtime($obj->revdate($revision));
+print "Date : Revision = $date_str : $revision\n\n";
+
+
+# list mode
+print "List mode\n";
+my @revisions = $obj->daterev(@date_array);
+foreach (@revisions) {
+ $date_str = gmtime($obj->revdate($_));
+ print "Date : Revision = $date_str : $_\n";
+}
+
+
+print "\n\n\n";
+print "Called as 1 argument method\n";
+print "Time number is $target_time\n";
+$revision = $obj->daterev($target_time);
+$date_str = gmtime($obj->revdate($revision));
+print "Date : Revision = $date_str : $revision\n\n";
+
+
+# list mode
+print "List mode\n";
+@revisions = $obj->daterev($target_time);
+foreach (@revisions) {
+ $date_str = gmtime($obj->revdate($_));
+ print "Date : Revision = $date_str : $_\n";
+}
+
diff --git a/cpan/dist/Rcs/examples/dates.pl b/cpan/dist/Rcs/examples/dates.pl
new file mode 100755
index 00000000..2d82a800
--- /dev/null
+++ b/cpan/dist/Rcs/examples/dates.pl
@@ -0,0 +1,35 @@
+#!/usr/local/bin/perl -w
+#------------------------------------------
+# Access dates hash
+#------------------------------------------
+use strict;
+use Rcs;
+
+#Rcs->bindir('/usr/bin');
+my $obj = Rcs->new;
+
+$obj->rcsdir("./project/RCS");
+$obj->workdir("./project/src");
+$obj->file("testfile");
+
+# sort by date
+my %dates_hash = $obj->dates;
+my $revision;
+my %dates;
+foreach $revision (keys %dates_hash) {
+ my $date = $dates_hash{$revision};
+ $dates{$date}{$revision} = 1;
+}
+
+my $date;
+foreach $date (reverse sort keys %dates) {
+ foreach $revision (keys %{ $dates{$date} }) {
+ my $date_str = localtime($date);
+ print "Revision : Date = $revision : $date_str\n";
+ }
+}
+
+# scalar mode returns most recent date
+print "\n";
+my $most_recent = localtime($obj->dates);
+print "Most recent revision date = $most_recent\n";
diff --git a/cpan/dist/Rcs/examples/green.txt b/cpan/dist/Rcs/examples/green.txt
new file mode 100644
index 00000000..8c5b40d2
--- /dev/null
+++ b/cpan/dist/Rcs/examples/green.txt
@@ -0,0 +1,85 @@
+From - Fri Sep 4 19:24:30 1998
+Return-Path: <greendjf@cvhp152.gpt.co.uk>
+Received: from cvis01.gpt.co.uk (cvis01.gpt.co.uk [195.99.244.34])
+ by onella.admc.com (8.9.0/8.9.0) with SMTP id DAA00464
+ for <craig@admc.com>; Thu, 3 Sep 1998 03:45:15 -0400
+Received: from cvfw01 by cvis01.gpt.co.uk with SMTP
+ (SMI-8.6/GPT-01b5) id IAA03221; Thu, 3 Sep 1998 08:34:39 +0100
+Received: from cvhp152 (cvhp152.gpt.co.uk) by cvhp99.gpt.co.uk with SMTP
+ (1.37.109.17/99-21) id AA007258250; Thu, 3 Sep 1998 08:37:30 +0100
+Received: from cvhp267.gpt.co.uk by cvhp152 with SMTP
+ (1.38.193.4/MAIL-15) id AA12641; Thu, 3 Sep 1998 08:37:30 +0100
+Sender: greendjf@cvhp152.gpt.co.uk
+Message-Id: <35EE4739.A3A04AD1@cvhp152.gpt.co.uk>
+Date: Thu, 03 Sep 1998 08:37:29 +0100
+From: David Green <greendjf@cvhp152.gpt.co.uk>
+Organization: GPT Ltd, U.K.
+X-Mailer: Mozilla 4.06 [en] (X11; I; HP-UX B.10.20 9000/715)
+Mime-Version: 1.0
+To: Craig <craig@admc.com>
+Subject: Re: Rcs.pm 0.07
+References: <359A81C3.3AD5@freter.com> <359B3C03.EF608041@cvhp152.gpt.co.uk> <359B6955.CCA0F605@freter.com> <35E67CD9.19DEDCE1@cvhp152.gpt.co.uk> <35EAA99F.471156A@freter.com> <35EC0CD8.C955E86B@cvhp152.gpt.co.uk> <35EDCEF8.1FB4517D@admc.com>
+Content-Type: text/plain; charset=us-ascii
+Content-Transfer-Encoding: 7bit
+
+Craig wrote:
+Craig,
+
+I've tried out your new code for daterev and it works ok except that my
+version of perl, 5.004_04, complained that $target_time was not defined
+(out of scope) a few lines up from the bottom of the method. I had to
+define $target_time before the if (@_ == 6) test.
+
+sub daterev {
+ my $self = shift;
+
+ # ensure date has all the elements
+ if (@_ != 1 and @_ != 6) {
+ croak "daterev must either 1 or 6 arguments";
+ }
+
+ my $target_time;
+ if (@_ == 6) { # string date passed
+ my($year, $mon, $mday, $hour, $min, $sec) = @_;
+
+ if($year !~ /^\d{4}$/) {
+ croak "year (1st param) must be 4 digit number";
+ }
+
+ $mon--; # convert to 0-11 range
+ $target_time = timegm($sec, $min, $hour, $mday, $mon, $year);
+ }
+ else { # system date passed
+ $target_time = shift;
+ }
+
+ if (not defined $self->{DATE}) {
+ _parse_rcs_header($self);
+ }
+
+
+ my @revisions;
+ my %dates;
+
+ my %dates_hash = %{$self->{DATE}};
+ foreach $revision (keys %dates_hash) {
+ my $date = $dates_hash{$revision};
+ $dates{$date}{$revision} = 1;
+ }
+
+ my $date;
+ foreach $date (reverse sort keys %dates) {
+ foreach $revision (keys %{ $dates{$date} }) {
+ push @revisions, $revision if $date <= $target_time;
+ }
+ }
+
+ return wantarray ? @revisions : $revisions[0];
+}
+
+
+Regards,
+
+David.
+____________________________________________________________________
+David Green, AD4541, GPT Coventry, (740) 2821 greendjf@ncp.gpt.co.uk
diff --git a/cpan/dist/Rcs/examples/mark.pl b/cpan/dist/Rcs/examples/mark.pl
new file mode 100755
index 00000000..01f4f98e
--- /dev/null
+++ b/cpan/dist/Rcs/examples/mark.pl
@@ -0,0 +1,21 @@
+#!/usr/local/bin/perl -w
+#------------------------------------------
+# Unlock RCS file
+#------------------------------------------
+use strict;
+use Rcs;
+
+Rcs->bindir('/usr/bin');
+Rcs->quiet(0);
+my $obj = Rcs->new;
+
+$obj->rcsdir("./project/RCS");
+$obj->workdir("./project/src");
+$obj->file("testfile");
+(my $symbol = shift) or die "Usage: $0 symbol [revision]\n";
+my $revision = shift || $obj->head;
+die "Revision $revision does not exist\n"
+ unless grep /^$revision$/, $obj->revisions;
+
+$obj->rcs("-n${symbol}:${revision}");
+
diff --git a/cpan/dist/Rcs/examples/parse.pl b/cpan/dist/Rcs/examples/parse.pl
new file mode 100755
index 00000000..50fb67c1
--- /dev/null
+++ b/cpan/dist/Rcs/examples/parse.pl
@@ -0,0 +1,38 @@
+#!/usr/local/bin/perl -w
+#------------------------------------------
+# Parse RCS archive file.
+#------------------------------------------
+use strict;
+use Rcs;
+
+Rcs->bindir('/usr/bin');
+my $obj = Rcs->new;
+
+$obj->rcsdir("./project/RCS");
+$obj->workdir("./project/src");
+$obj->file("testfile");
+
+my $head_rev = $obj->head;
+my $locker = $obj->lock;
+my $author = $obj->author;
+my @access = $obj->access;
+my @revisions = $obj->revisions;
+
+my $filename = $obj->file;
+
+if ($locker) {
+ print "Head revision $head_rev is locked by $locker\n";
+}
+else {
+ print "Head revision $head_rev is unlocked\n";
+}
+
+if (@access) {
+ print "\nThe following users are on the access list of file $filename\n";
+ map { print "User: $_\n"} @access;
+}
+
+print "\nList of all revisions of $filename\n";
+foreach (@revisions) {
+ print "Revision: $_\n";
+}
diff --git a/cpan/dist/Rcs/examples/pause b/cpan/dist/Rcs/examples/pause
new file mode 100644
index 00000000..4141ffd6
--- /dev/null
+++ b/cpan/dist/Rcs/examples/pause
@@ -0,0 +1 @@
+http://www.perl.com/CPAN-local/modules/04pause.html
diff --git a/cpan/dist/Rcs/examples/project/src/Rcs.pm b/cpan/dist/Rcs/examples/project/src/Rcs.pm
new file mode 100644
index 00000000..03241de1
--- /dev/null
+++ b/cpan/dist/Rcs/examples/project/src/Rcs.pm
@@ -0,0 +1,1320 @@
+package Rcs;
+require 5.001;
+use strict;
+use Carp;
+use Time::Local;
+use vars qw($VERSION $revision);
+
+#------------------------------------------------------------------
+# global stuff
+#------------------------------------------------------------------
+$VERSION = '0.08';
+$revision = '$Id: Rcs.pm,v 1.14 1998/07/23 01:00:23 freter Exp freter $';
+my $Dir_Sep = ($^O eq 'MSWin32') ? '\\' : '/';
+my $Exe_Ext = ($^O eq 'MSWin32') ? '.exe' : '';
+my $Rcs_Bin_Dir = '/usr/local/bin';
+my $Rcs_Dir = '.' . $Dir_Sep . 'RCS';
+my $Work_Dir = '.';
+my $Quiet = 1; # RCS quiet mode
+my $Arc_Ext = ',v';
+
+#------------------------------------------------------------------
+# RCS object constructor
+#------------------------------------------------------------------
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = {};
+
+ # provide default values for system stuff
+ $self->{"_BINDIR"} = \$Rcs_Bin_Dir;
+ $self->{"_QUIET"} = \$Quiet;
+ $self->{"_RCSDIR"} = \$Rcs_Dir;
+ $self->{"_WORKDIR"} = \$Work_Dir;
+ $self->{"_ARCEXT"} = \$Arc_Ext;
+
+ $self->{FILE} = undef;
+ $self->{ARCFILE} = undef;
+ $self->{AUTHOR} = undef;
+ $self->{COMMENTS} = undef;
+ $self->{DATE} = undef;
+ $self->{LOCK} = undef;
+ $self->{ACCESS} = [];
+ $self->{REVISIONS} = [];
+ $self->{REVINFO} = undef;
+ $self->{STATE} = undef;
+ $self->{SYMBOLS} = undef;
+ bless($self, $class);
+ return $self;
+}
+
+#------------------------------------------------------------------
+# access
+# Access list of archive file.
+#------------------------------------------------------------------
+sub access {
+ my $self = shift;
+
+ if (not @{ $self->{ACCESS} }) {
+ _parse_rcs_header($self);
+ }
+
+ # dereference revisions list
+ my @access = @{ $self->{ACCESS} };
+
+ return @access;
+}
+
+#------------------------------------------------------------------
+# arcext
+# Set the RCS archive file extension (default is ',v').
+#------------------------------------------------------------------
+sub arcext {
+ my $self = shift;
+
+ # called as object method
+ if (ref $self) {
+ if (@_) { ${ $self->{"_ARCEXT"} } = shift };
+ return ${ $self->{"_ARCEXT"} };
+ }
+
+ # called as class method
+ else {
+ if (@_) { $Arc_Ext = shift; }
+ return $Arc_Ext;
+ }
+}
+
+#------------------------------------------------------------------
+# arcfile
+# Name of RCS archive file.
+# If not set then return name of working file with RCS
+# extension (',v').
+#------------------------------------------------------------------
+sub arcfile {
+ my $self = shift;
+ if (@_) { $self->{ARCFILE} = shift }
+ return $self->{ARCFILE} || $self->{FILE} . ${ $self->{"_ARCEXT"} };
+}
+
+#------------------------------------------------------------------
+# author
+# Return the author of an RCS revision.
+# If revision is not provided, default to 'head' revision.
+#------------------------------------------------------------------
+sub author {
+ my $self = shift;
+
+ if (not defined $self->{AUTHOR}) {
+ _parse_rcs_header($self);
+ }
+ my $revision = shift || $self->{HEAD};
+
+ # dereference author hash
+ my %author_array = %{ $self->{AUTHOR} };
+
+ return $author_array{$revision};
+}
+
+#------------------------------------------------------------------
+# bindir
+# Set the bin directory in which the RCS distribution programs
+# reside.
+#------------------------------------------------------------------
+sub bindir {
+ my $self = shift;
+
+ # called as object method
+ if (ref $self) {
+ if (@_) { ${ $self->{"_BINDIR"} } = shift };
+ return ${ $self->{"_BINDIR"} };
+ }
+
+ # called as class method
+ else {
+ if (@_) { $Rcs_Bin_Dir = shift };
+ return $Rcs_Bin_Dir;
+ }
+}
+
+#------------------------------------------------------------------
+# ci
+# Execute RCS 'ci' program.
+# Make archive filename same as working filename unless
+# specifically set.
+#------------------------------------------------------------------
+sub ci {
+ my $self = shift;
+ my @param = @_;
+
+ my $ciprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'ci' . $Exe_Ext;
+ my $rcsdir = ${ $self->{"_RCSDIR"} };
+ my $workdir = ${ $self->{"_WORKDIR"} };
+ my $file = $self->{FILE};
+ my $arcfile = $self->{ARCFILE} || $file;
+
+ my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} };
+ my $workfile = $workdir . $Dir_Sep . $file;
+ push @param, $archive_file, $workfile;
+ unshift @param, "-q" if ${ $self->{"_QUIET"} }; # quiet mode
+
+ # run program
+ croak "ci program $ciprog not found" unless -e $ciprog;
+ croak "ci program $ciprog not executable" unless -x $ciprog;
+ system($ciprog, @param) == 0 or croak "$!";
+
+ # re-parse RCS file and clear comments hash
+ _parse_rcs_header($self);
+ $self->{COMMENTS} = undef;
+}
+
+#------------------------------------------------------------------
+# co
+# Execute RCS 'co' program.
+# Make archive filename same as working filename unless
+# specifically set.
+#------------------------------------------------------------------
+sub co {
+ my $self = shift;
+ my @param = @_;
+
+ my $coprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'co' . $Exe_Ext;
+ my $rcsdir = ${ $self->{"_RCSDIR"} };
+ my $workdir = ${ $self->{"_WORKDIR"} };
+ my $file = $self->{FILE};
+ my $arcfile = $self->{ARCFILE} || $file;
+
+ my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} };
+ my $workfile = $workdir . $Dir_Sep . $file;
+ push @param, $archive_file, $workfile;
+ unshift @param, "-q" if ${ $self->{"_QUIET"} }; # quiet mode
+
+ # run program
+ croak "co program $coprog not found" unless -e $coprog;
+ croak "co program $coprog not executable" unless -x $coprog;
+ system($coprog, @param) == 0 or croak "$!";
+
+ # re-parse RCS file and clear comments hash
+ _parse_rcs_header($self);
+ $self->{COMMENTS} = undef;
+}
+
+#------------------------------------------------------------------
+# comments
+#------------------------------------------------------------------
+sub comments {
+ my $self = shift;
+
+ if (not defined $self->{COMMENTS}) {
+ _parse_rcs_body($self);
+ }
+
+ return %{$self->{COMMENTS}};
+}
+
+#------------------------------------------------------------------
+# daterev
+# Returns a revision which was current at a specified date/time.
+# 0 is returned if all revisions are newer than the date
+# specified. This usually means the file did not exist on that
+# date.
+# This takes 6 parameters, year (4 digit year), month (1-12), day
+# of month (1-31), hour (0-23), minute (0-59) and second (0-59).
+#------------------------------------------------------------------
+sub daterev {
+ my $self = shift;
+ my($year, $mon, $mday, $hour, $min, $sec) = @_;
+
+ # ensure date has all the elements
+ if(@_ != 6) {
+ croak "daterev must have 6 element date/time (year, month, day, hour, min, sec)";
+ }
+
+ if($year !~ /^\d{4}$/) {
+ croak "year (1st param) must be 4 digit number";
+ }
+
+ if (not defined $self->{DATE}) {
+ _parse_rcs_header($self);
+ }
+
+ $mon--; # convert to 0-11 range
+ my $target_time = timegm($sec, $min, $hour, $mday, $mon, $year);
+ my @revisions;
+ my %dates;
+
+ my %dates_hash = %{$self->{DATE}};
+ foreach $revision (keys %dates_hash) {
+ my $date = $dates_hash{$revision};
+ $dates{$date}{$revision} = 1;
+ }
+
+ my $date;
+ foreach $date (reverse sort keys %dates) {
+ foreach $revision (keys %{ $dates{$date} }) {
+ push @revisions, $revision if $date <= $target_time;
+ }
+ }
+
+ return wantarray ? @revisions : $revisions[0];
+}
+
+#------------------------------------------------------------------
+# dates
+# Return a hash of revision dates, keyed on revision, when called
+# in list mode.
+# Return the most recent date when called in scalar mode.
+#
+# RCS stores dates in GMT.
+# The date values are system dates.
+#------------------------------------------------------------------
+sub dates {
+ my $self = shift;
+
+ if (not defined $self->{DATE}) {
+ _parse_rcs_header($self);
+ }
+
+ my %DatesHash = %{$self->{DATE}};
+ my @dates_list = sort {$b<=>$a} values %DatesHash;
+ my $MostRecent = $dates_list[0];
+
+ return wantarray ? %DatesHash : $MostRecent;
+}
+
+#------------------------------------------------------------------
+# file
+# Name of working file.
+#------------------------------------------------------------------
+sub file {
+ my $self = shift;
+ if (@_) { $self->{FILE} = shift }
+ return $self->{FILE};
+}
+
+#------------------------------------------------------------------
+# head
+# Return the head revision.
+#------------------------------------------------------------------
+sub head {
+ my $self = shift;
+
+ if (not defined $self->{HEAD}) {
+ _parse_rcs_header($self);
+ }
+ return $self->{HEAD};
+}
+
+#------------------------------------------------------------------
+# lock
+# Return user who has file locked.
+#------------------------------------------------------------------
+sub lock {
+ my $self = shift;
+
+ if (not defined $self->{LOCK}) {
+ _parse_rcs_header($self);
+ }
+ return $self->{LOCK};
+}
+
+#------------------------------------------------------------------
+# quiet
+# Set or un-set RCS quiet mode.
+#------------------------------------------------------------------
+sub quiet {
+ my $self = shift;
+
+ # called as object method
+ if (ref $self) {
+
+ # set/un-set quiet mode
+ if (@_) {
+ my $mode = shift;
+ croak "Passed parameter must be either '0' or '1'"
+ unless $mode == 0 or $mode == 1;
+ ${ $self->{"_QUIET"} } = $mode;
+ return ${ $self->{"_QUIET"} };
+ }
+
+ # access quiet mode
+ else {
+ return ${ $self->{"_QUIET"} };
+ }
+ }
+
+ # called as class method
+ else {
+
+ # set/un-set quiet mode
+ if (@_) {
+ my $mode = shift;
+ croak "Passed parameter must be either '0' or '1'"
+ unless $mode == 0 or $mode == 1;
+ $Quiet = $mode;
+ return $Quiet;
+ }
+
+ # access quiet mode
+ else {
+ return $Quiet;
+ }
+ }
+}
+
+#------------------------------------------------------------------
+# rcs
+# Execute RCS 'rcs' program.
+# Make archive filename same as working filename unless
+# specifically set.
+#------------------------------------------------------------------
+sub rcs {
+ my $self = shift;
+ my @param = @_;
+
+ my $rcsprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'rcs' . $Exe_Ext;
+ my $rcsdir = ${ $self->{"_RCSDIR"} };
+ my $workdir = ${ $self->{"_WORKDIR"} };
+ my $file = $self->{FILE};
+ my $arcfile = $self->{ARCFILE} || $file;
+
+ my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} };
+ my $workfile = $workdir . $Dir_Sep . $file;
+ push @param, $archive_file, $workfile;
+ unshift @param, "-q" if ${ $self->{"_QUIET"} }; # quiet mode
+
+ # run program
+ croak "rcs program $rcsprog not found" unless -e $rcsprog;
+ croak "rcs program $rcsprog not executable" unless -x $rcsprog;
+ system($rcsprog, @param) == 0 or croak "$?";
+
+ # re-parse RCS file and clear comments hash
+ _parse_rcs_header($self);
+ $self->{COMMENTS} = undef;
+}
+
+#------------------------------------------------------------------
+# rcsclean
+# Execute RCS 'rcsclean' program.
+#------------------------------------------------------------------
+sub rcsclean {
+ my $self = shift;
+ my @param = @_;
+
+ my $rcscleanprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'rcsclean' . $Exe_Ext;
+ my $rcsdir = ${ $self->{"_RCSDIR"} };
+ my $workdir = ${ $self->{"_WORKDIR"} };
+ my $file = $self->{FILE};
+ my $arcfile = $self->{ARCFILE} || $file;
+
+ my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} };
+ my $workfile = $workdir . $Dir_Sep . $file;
+ push @param, $archive_file, $workfile;
+
+ # run program
+ croak "rcsclean program $rcscleanprog not found" unless -e $rcscleanprog;
+ croak "rcsclean program $rcscleanprog not executable" unless -x $rcscleanprog;
+ system($rcscleanprog, @param) == 0 or croak "$?";
+
+ # re-parse RCS file and clear comments hash
+ _parse_rcs_header($self);
+ $self->{COMMENTS} = undef;
+}
+
+#------------------------------------------------------------------
+# rcsdiff
+# Execute RCS 'rcsdiff' program.
+# Calling in list context returns the output of rcsdiff, while
+# calling in scalar context returns the return status of the
+# rcsdiff program.
+#------------------------------------------------------------------
+sub rcsdiff {
+ my $self = shift;
+ my @param = @_;
+
+ my $rcsdiff_prog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'rcsdiff' . $Exe_Ext;
+ my $rcsdir = ${ $self->{"_RCSDIR"} };
+ my $arcfile = $self->{ARCFILE} || $self->{FILE};
+ $arcfile = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} };
+ my $workfile = $self->workdir . $Dir_Sep . $self->file;
+
+ # un-taint parameter string
+ unshift @param, "-q" if ${ $self->{"_QUIET"} }; # quiet mode
+ my $param_str = join(' ', @param);
+ $param_str =~ s/([\w-]+)/$1/g;
+
+ croak "rcsdiff program $rcsdiff_prog not found" unless -e $rcsdiff_prog;
+ croak "rcsdiff program $rcsdiff_prog not executable" unless -x $rcsdiff_prog;
+ open(DIFF, "$rcsdiff_prog $param_str $arcfile $workfile |");
+ my @diff_output = <DIFF>;
+
+ # rcsdiff returns exit status 0 for no differences, 1 for differences,
+ # and 2 for error condition.
+ close DIFF;
+ my $status = $?;
+ croak "$rcsdiff_prog failed" if $status == 2;
+ return wantarray ? @diff_output : $status;
+}
+
+#------------------------------------------------------------------
+# rcsdir
+# Location of 'RCS' archive directory.
+#------------------------------------------------------------------
+sub rcsdir {
+ my $self = shift;
+
+ # called as object method
+ if (ref $self) {
+ if (@_) { ${ $self->{"_RCSDIR"} } = shift }
+ return ${ $self->{"_RCSDIR"} };
+ }
+
+ # called as class method
+ else {
+ if (@_) { $Rcs_Dir = shift }
+ return $Rcs_Dir;
+ }
+}
+
+#------------------------------------------------------------------
+# revdate
+# Return the revision date of an RCS revision.
+# If revision is not provided, default to 'head' revision.
+#
+# RCS stores dates in GMT. This method will return dates relative
+# to the local time zone.
+#------------------------------------------------------------------
+sub revdate {
+ my $self = shift;
+
+ if (not defined $self->{DATE}) {
+ _parse_rcs_header($self);
+ }
+ my $revision = shift || $self->{HEAD};
+
+ # dereference date hash
+ my %date_array = %{ $self->{DATE} };
+ my $date_str = $date_array{$revision};
+
+ return wantarray ? localtime($date_str) : $date_str;
+}
+
+#------------------------------------------------------------------
+# revisions
+#------------------------------------------------------------------
+sub revisions {
+ my $self = shift;
+
+ if (not @{ $self->{REVISIONS} }) {
+ _parse_rcs_header($self);
+ }
+
+ # dereference revisions list
+ my @revisions = @{ $self->{REVISIONS} };
+
+ @revisions;
+}
+
+#------------------------------------------------------------------
+# rlog
+# Execute RCS 'rlog' program.
+# Make archive filename same as working filename unless
+# specifically set.
+#------------------------------------------------------------------
+sub rlog {
+ my $self = shift;
+ my @param = @_;
+
+ my $rlogprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'rlog' . $Exe_Ext;
+ my $rcsdir = ${ $self->{"_RCSDIR"} };
+ my $arcfile = $self->{ARCFILE} || $self->{FILE};
+
+ # un-taint parameter string
+ my $param_str = join(' ', @param);
+ $param_str =~ s/([\w-]+)/$1/g;
+
+ my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} };
+ croak "rlog program $rlogprog not found" unless -e $rlogprog;
+ croak "rlog program $rlogprog not executable" unless -x $rlogprog;
+ open(RLOG, "$rlogprog $param_str $archive_file |");
+
+ my @logoutput = <RLOG>;
+ close RLOG;
+ croak "$rlogprog failed" if $?;
+ @logoutput;
+}
+
+#------------------------------------------------------------------
+# state
+# If revision is not provided, default to 'head' revision
+#------------------------------------------------------------------
+sub state {
+ my $self = shift;
+
+ if (not defined $self->{STATE}) {
+ _parse_rcs_header($self);
+ }
+ my $revision = shift || $self->{HEAD};
+
+ # dereference author hash
+ my %state_array = %{ $self->{STATE} };
+
+ return $state_array{$revision};
+}
+
+#------------------------------------------------------------------
+# symbol
+# If revision is not provided, default to 'head' revision
+#------------------------------------------------------------------
+sub symbol {
+ my $self = shift;
+
+ if (not defined $self->{SYMBOLS}) {
+ _parse_rcs_header($self);
+ }
+ my $revision = shift || $self->{HEAD};
+
+ # dereference symbols hash
+ my %sym_array = %{ $self->{SYMBOLS} };
+
+ return '' if not defined $sym_array{$revision};
+
+ my @symbols = @{ $sym_array{$revision} };
+
+ # return only first array element if user wants scalar
+ return wantarray ? @symbols : $symbols[0];
+}
+
+#------------------------------------------------------------------
+# symbols
+# Returns hash of all revisions keyed on symbol defined against file.
+#------------------------------------------------------------------
+sub symbols {
+ my $self = shift;
+
+ if(not defined $self->{SYMBOLS}) {
+ _parse_rcs_header($self);
+ }
+
+ my %symbols;
+
+ # loop through each revision
+ my $rev;
+ foreach $rev (@{ $self->{REVISIONS} }) {
+ my $sym;
+ foreach $sym (@{ $self->{SYMBOLS}->{$rev} }) {
+ $symbols{$sym} = $rev;
+ }
+ }
+ return %symbols;
+}
+
+#------------------------------------------------------------------
+# symrev
+# Returns the revision against which a specified symbol was
+# defined. If the symbol was not defined against any version
+# of this file, 0 is returned.
+#------------------------------------------------------------------
+sub symrev {
+ my $self = shift;
+ my $sym = shift;
+ if(! defined $sym) {
+ croak "You must supply a symbol to symrev";
+ }
+
+ if (not defined $self->{SYMBOLS}) {
+ _parse_rcs_header($self);
+ }
+
+ my $ret_rev = 0;
+ my %symbols;
+
+ # loop through each revision
+ my $rev;
+ REV_LOOP:
+ foreach $rev (@{ $self->{REVISIONS} }) {
+ # loop through each symbol defined against
+ # this revision
+ my $s;
+ foreach $s (@{ $self->{SYMBOLS}->{$rev} }) {
+
+ # store each revision matching the pattern
+ if (wantarray) {
+ $symbols{$s} = $rev if $s =~ /$sym/;
+ }
+
+ # if it's the one we're looking for, we can
+ # quit as we've found the revision we want
+ else {
+ if($s eq $sym) {
+ $ret_rev = $rev;
+ last REV_LOOP;
+ }
+ }
+ }
+ }
+
+ return wantarray ? %symbols : $ret_rev;
+}
+
+#------------------------------------------------------------------
+# workdir
+# Location of working directory.
+#------------------------------------------------------------------
+sub workdir {
+ my $self = shift;
+
+ # called as object method
+ if (ref $self) {
+ if (@_) { ${ $self->{"_WORKDIR"} } = shift }
+ return ${ $self->{"_WORKDIR"} };
+ }
+
+ # called as class method
+ else {
+ if (@_) { $Work_Dir = shift }
+ return $Work_Dir;
+ }
+}
+
+#------------------------------------------------------------------
+# _parse_rcs_body
+# Private function
+#------------------------------------------------------------------
+sub _parse_rcs_body {
+
+ my $self = shift;
+ local $_;
+
+ my %comments;
+
+ my $rcsdir = ${ $self->{"_RCSDIR"} };
+ my $file = $self->{FILE};
+ my $rcs_file = $rcsdir . $Dir_Sep . $file . ${ $self->{"_ARCEXT"} };
+
+ # parse RCS archive file
+ open RCS_FILE, $rcs_file or croak "Unable to open $rcs_file";
+
+ # skip header info and get description
+ DESC: while (<RCS_FILE>) {
+ if (/^desc$/) {
+ $comments{0} = '';
+ $_ = <RCS_FILE>;
+ s/^\@//; # remove leading '@'
+ while (1) {
+ last DESC if /^\@$/;
+ s/\@\@/\@/g; # RCS replaces single '@' with '@@'
+ $comments{0} .= $_;
+ $_ = <RCS_FILE>;
+ }
+ }
+ }
+
+ # parse revision comments
+ my $revision;
+ REVISION: while (<RCS_FILE>) {
+ if (/^[\d\.]+$/) {
+ chomp($revision = $_);
+ $_ = <RCS_FILE>;
+ if (/^log$/) {
+ $comments{$revision} = '';
+ $_ = <RCS_FILE>;
+ s/^\@//; # remove leading '@'
+ while (1) {
+ next REVISION if /^\@$/;
+ s/\@\@/\@/g; # RCS replaces single '@' with '@@'
+ $comments{$revision} .= $_;
+ $_ = <RCS_FILE>;
+ }
+ }
+ }
+ }
+
+ # loop through 'text' section to avoid capturing false comments
+ continue {
+ if (/^text$/) {
+ while (<RCS_FILE>) {last if /^\@$/}
+ }
+ }
+
+ close RCS_FILE;
+
+ $self->{COMMENTS} = \%comments;
+}
+
+#------------------------------------------------------------------
+# _parse_rcs_header
+# Private function
+# Directly parse the RCS archive file.
+#------------------------------------------------------------------
+sub _parse_rcs_header {
+
+ my $self = shift;
+ local $_;
+
+ my ($head, $lock);
+ my (@access_list, @revisions);
+ my (%author, %date, %state, %symbols);
+
+ my $rcsdir = ${ $self->{"_RCSDIR"} };
+ my $file = $self->{FILE};
+ my $rcs_file = $rcsdir . $Dir_Sep . $file . ${ $self->{"_ARCEXT"} };
+
+ # parse RCS archive file
+ open RCS_FILE, $rcs_file
+ or croak "Unable to open $rcs_file";
+ while (<RCS_FILE>) {
+ next if /^\s*$/; # skip blank lines
+ last if /^desc$/; # end of header info
+
+ # get head revision
+ if (/^head\s/) {
+ ($head) = /^head\s+(.*?);$/;
+ next;
+ }
+
+ # get access list
+ if (/^access$/) {
+ while (<RCS_FILE>) {
+ chomp;
+ s/\s//g; # remove all whitespace
+ push @access_list, (split(/;/))[0];
+ last if /;$/;
+ }
+ next;
+ }
+
+ # get locker
+ # get symbols
+ if (/^symbols$/) {
+ while (<RCS_FILE>) {
+ chomp;
+ s/\s//g; # remove all whitespace
+ my ($sym, $rev) = split(/:/);
+ $rev =~ s/;$//;
+ push @{ $symbols{$rev} }, $sym;
+ last if /;$/;
+ }
+ next;
+ }
+
+ # get locker
+ if (/^locks/) {
+
+ # file not locked
+ if (/strict/) {
+ $lock = '';
+ next;
+ }
+
+ # get user who has file locked
+ my $next_line = <RCS_FILE>; # read next line
+ ($lock) = $next_line =~ m/^\s*(\w+):/;
+ next;
+ }
+
+ # get all revisions
+ if (/^\d+\.\d+/) {
+ chomp;
+ push @revisions, $_;
+
+ # get author, state and date of each revision
+ my $next_line = <RCS_FILE>;
+ chop(my $author = (split(/\s+/, $next_line))[3]);
+ chop(my $state = (split(/\s+/, $next_line))[5]);
+ chop(my $date = (split(/\s+/, $next_line))[1]);
+
+ # store date as date number
+ my ($year, $mon, $mday, $hour, $min, $sec) = split(/\./, $date);
+ $mon--; # convert to 0-11 range
+ my @date = ($sec,$min,$hour,$mday,$mon,$year);
+
+ # store value in hash using revision as key
+ $author{$_} = $author;
+ $state{$_} = $state;
+ $date{$_} = timegm(@date);
+ }
+ }
+ close RCS_FILE;
+
+ $self->{HEAD} = $head;
+ $self->{LOCK} = $lock;
+ $self->{ACCESS} = \@access_list;
+ $self->{REVISIONS} = \@revisions;
+ $self->{AUTHOR} = \%author;
+ $self->{DATE} = \%date;
+ $self->{STATE} = \%state;
+ $self->{SYMBOLS} = \%symbols;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Rcs - Perl Object Class for Revision Control System (RCS).
+
+=head1 SYNOPSIS
+
+ use Rcs;
+
+=head1 DESCRIPTION
+
+This Perl module provides an object oriented interface to access
+B<Revision Control System (RCS)> utilities. RCS must be installed on
+the system prior to using this module. This module should simplify
+the creation of an RCS front-end.
+
+=head2 OBJECT CONSTRUCTOR
+
+The B<new> method may be used as either a class method or an object
+method to create a new object.
+
+ # called as class method
+ $obj = Rcs->new;
+
+ # called as object method
+ $newobj = $obj->new;
+
+=head2 CLASS METHODS
+
+Besides the object constructor, there are three class methods provided
+which effect any newly created objects.
+
+The B<arcext> method sets the RCS archive extension, which is ',v' by
+default.
+
+ # set/unset RCS archive extension
+ Rcs->arcext(''); # set no archive extension
+ Rcs->arcext(',v'); # set archive extension to ',v'
+ $arc_ext = Rcs->arcext(); # get current archive extension
+
+The B<bindir> method sets the directory path where the RCS executables
+(i.e. rcs, ci, co) are located. The default location is '/usr/local/bin'.
+
+ # set RCS bin directory
+ Rcs->bindir('/usr/bin');
+
+ # access RCS bin directory
+ $bin_dir = Rcs->bindir;
+
+The B<quiet> method sets/unsets the quiet mode for the RCS executables.
+Quiet mode is set by default.
+
+ # set/unset RCS quiet mode
+ Rcs->quiet(0); # unset quiet mode
+ Rcs->quiet(1); # set quiet mode
+
+ # access RCS quiet mode
+ $quiet_mode = Rcs->quiet;
+
+These methods may also be called as object methods.
+
+ $obj->arcext('');
+ $obj->bindir('/usr/bin');
+ $obj->quiet(0);
+
+=head2 OBJECT ATTRIBUTE METHODS
+
+These methods set the attributes of the RCS object.
+
+The B<file> method is used to set the name of the RCS working file. The
+filename must be set before invoking any access of modifier methods on the
+object.
+
+ $obj->file('mr_anderson.pl');
+
+The B<arcfile> method is used to set the name of the RCS archive file.
+Using this method is optional, as the other methods will assume the archive
+filename is the same as the working file unless specified otherwise. The
+RCS archive extension (default ',v') is automatically added to the filename.
+
+ $obj->arcfile('principle_mcvicker.pl');
+
+The B<workdir> methods set the path of the RCS working directory. If not
+specified, default path is '.' (current working directory).
+
+ $obj->workdir('/usr/local/source');
+
+The B<rcsdir> methods set the path of the RCS archive directory. If not
+specified, default path is './RCS'.
+
+ $obj->rcsdir('/usr/local/archive');
+
+=head2 RCS PARSE METHODS
+
+This class provides methods to directly parse the RCS archive file.
+
+The B<access> method returns a list of all user on the access list.
+
+ @access_list = $obj->access;
+
+The B<author> method returns the author of the revision. The head revision
+is used if no revision argument is passed to method.
+
+ # returns the author of revision '1.3'
+ $author = $obj->author('1.3');
+
+ # returns the authos of the head revision
+ $author = $obj->author;
+
+The B<head> method returns the head revision.
+
+ $head = $obj->head;
+
+The B<lock> method returns the locker of the revision. The method returns
+null if the revision is unlocked. The head revision is used if no revision
+argument is passed to method.
+
+ # returns locker of revision '1.3'
+ $locker = $obj->lock('1.3');
+
+ # returns locker of head revision
+ $locker = $obj->lock;
+
+The B<revisions> method returns a list of all revisions of archive file.
+
+ @revisions = $obj->revisions;
+
+The B<state> method returns the state of the revision. The head revision
+is used if no revision argument is passed to method.
+
+ # returns state of revision '1.3'
+ $state = $obj->state('1.3');
+
+ # returns state of head revision
+ $state = $obj->state;
+
+The B<symbol> method returns the symbol(s) associated with a revision.
+If called in list context, method returns all symbols associated with
+revision. If called in scalar context, method returns last symbol
+assciated with a revision. The head revision is used if no revision argument
+is passed to method.
+
+ # list context, returns all symbols associated with revision 1.3
+ @symbols = $obj->symbol('1.3');
+
+ # list context, returns all symbols associated with head revision
+ @symbols = $obj->symbol;
+
+ # scalar context, returns last symbol associated with revision 1.3
+ $symbol = $obj->symbol('1.3');
+
+ # scalar context, returns last symbol associated with head revision
+ $symbol = $obj->symbol;
+
+The B<symbols> method returns a hash, keyed by symbol, of all of the revisions
+associated with the file.
+
+ %symbols = $obj->symbols;
+ foreach $sym (keys %symbols) {
+ $rev = $symbols{$sym};
+ }
+
+The B<revdate> method returns the date of a revision. The returned date format
+is the same as the localtime format. When called as a scalar, it returns the
+system date number. If called is list context, the list
+($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) is returned.
+
+ # scalar mode
+ $scalar_date = $obj->revdate;
+ print "Scalar date number = $scalar_date\n";
+ $date_str = localtime($scalar_date);
+ print "Scalar date string = $date_str\n";
+
+ # list mode
+ @list_date = $obj->revdate;
+ print "List date = @list_date\n";
+
+The B<dates> method returns a hash of revision dates, keyed on revision. The
+hash values are system date numbers. When called in scalar mode, the method
+returns the most recent revision date.
+
+ # list mode
+ %DatesHash = obj->dates;
+ @dates_list = sort {$b<=>$a} values %DatesHash;
+ $MostRecent = $dates_list[0];
+
+ # scalar mode
+ $most_recent = $obj->dates;
+ print "Most recent date = $most_recent\n";
+ $most_recent_str = localtime($most_recent);
+ print "Most recent date string = $most_recent_str\n";
+
+The B<symrev> method returns the revision against which a specified symbol was
+defined. If the symbol was not defined against any version of this file, 0 is
+returned.
+
+ # gets revision that has 'MY_SYMBOL' defined against it
+ $rev = symrev('MY_SYMBOL');
+
+The B<daterev> method returns a revision which was current at a specified
+date/time. If all revisions are newer than the specified date/time, i.e. the
+file did not exist then, 0 is returned.
+
+ # gets revision that was active on 25th June 1998 16:45:30
+ $rev = daterev(1998, 6, 25, 16, 45, 30);
+
+The B<comments> method returns a hash of revision comments, keyed on revision.
+A key value of 0 returns the description.
+
+ %comments = $obj->comments;
+ $description = $comments{0};
+ $comment_1_3 = $comments{'1.3'};
+
+=head2 RCS SYSTEM METHODS
+
+These methods invoke the RCS system utilities.
+
+The B<ci> method calls the RCS ci program.
+
+ # check in, and then check out in unlocked state
+ $obj->ci('-u');
+
+The B<co> method calls the RCS co program.
+
+ # check out in locked state
+ $obj->co('-l');
+
+The B<rcs> method calls the RCS rcs program.
+
+ # lock file
+ $obj->rcs('-l');
+
+The B<rcsdiff> method calls the RCS rcsdiff program. When called in
+list context, this method returns the outpout of the rcsdiff program.
+When called in scalar context, this method returns the return status of
+the rcsdiff program. The return status is 0 for the same, 1 for some
+differences, and 2 for error condition.
+
+When called without parameters, rcsdiff does a diff between the current
+working file, and the last revision checked in.
+
+ # call in list context
+ @diff_output = $obj->rcsdiff;
+
+ # call in scalar context
+ $changed = $obj->rcsdiff;
+ if ($changed) {
+ print "Working file has changed\n";
+ }
+
+Call rcsdiff with parameters to do a diff between any two revisions.
+
+ @diff_output = $obj->rcsdiff('-r1.2', '-r1.1');
+
+The B<rlog> method calls the RCS rlog program. This method returns the
+output of the rlog program.
+
+ # get complete log output
+ @rlog_complete = $obj->rlog;
+
+ # called with '-h' switch outputs only header information
+ @rlog_header = $obj->rlog('-h');
+ print @rlog_header;
+
+The B<rcsclean> method calls the RCS rcsclean program.
+
+ # remove working file
+ $obj->rcsclean;
+
+
+=head1 EXAMPLES
+
+=head2 CREATE ACCESS LIST
+
+Using method B<rcs> with the B<-a> switch allows you to add users to
+the access list of an RCS archive file.
+
+ use Rcs;
+ $obj = Rcs->new;
+
+ $obj->rcsdir("./project_tree/archive");
+ $obj->workdir("./project_tree/src");
+ $obj->file("cornholio.pl");
+
+Methos B<rcs> invokes the RCS utility rcs with the same parameters.
+
+ @users = qw(beavis butthead);
+ $obj->rcs("-a@users");
+
+Calling method B<access> returns list of users on access list.
+
+ $filename = $obj->file;
+ @access_list = $obj->access;
+ print "Users @access_list are on the access list of $filename\n";
+
+
+=head2 PARSE RCS ARCHIVE FILE
+
+Set class variables and create 'RCS' object.
+Set bin directory where RCS programs (e.g. rcs, ci, co) reside. The
+default is '/usr/local/bin'. This sets the bin directory for all objects.
+
+ use Rcs;
+ Rcs->bindir('/usr/bin');
+ $obj = Rcs->new;
+
+Set information regarding RCS object. This information includes name of the
+working file, directory of working file ('.' by default), and RCS archive
+directory ('./RCS' by default).
+
+ $obj->rcsdir("./project_tree/archive");
+ $obj->workdir("./project_tree/src");
+ $obj->file("cornholio.pl");
+
+ $head_rev = $obj->head;
+ $locker = $obj->lock;
+ $author = $obj->author;
+ @access = $obj->access;
+ @revisions = $obj->revisions;
+
+ $filename = $obj->file;
+
+ if ($locker) {
+ print "Head revision $head_rev is locked by $locker\n";
+ }
+ else {
+ print "Head revision $head_rev is unlocked\n";
+ }
+
+ if (@access) {
+ print "\nThe following users are on the access list of file $filename\n";
+ map { print "User: $_\n"} @access;
+ }
+
+ print "\nList of all revisions of $filename\n";
+ foreach $rev (@revisions) {
+ print "Revision: $rev\n";
+ }
+
+=head2 CHECK-IN FILE
+
+Set class variables and create 'RCS' object.
+Set bin directory where RCS programs (e.g. rcs, ci, co) reside. The
+default is '/usr/local/bin'. This sets the bin directory for all objects.
+
+ use Rcs;
+ Rcs->bindir('/usr/bin');
+ Rcs->quiet(0); # turn off quiet mode
+ $obj = Rcs->new;
+
+Set information regarding RCS object. This information includes name of
+working file, directory of working file ('.' by default), and RCS archive
+directory ('./RCS' by default).
+
+ $obj->file('cornholio.pl');
+
+ # Set RCS archive directory, is './RCS' by default
+ $obj->rcsdir("./project_tree/archive");
+
+ # Set working directory, is '.' by default
+ $obj->workdir("./project_tree/src");
+
+Check in file using B<-u> switch. This will check in the file, and will then
+check out the file in an unlocked state. The B<-m> switch is used to set the
+revision comment.
+
+Command:
+
+ $obj->ci('-u', '-mRevision Comment');
+
+is equivalent to commands:
+
+ $obj->ci('-mRevision Comment');
+ $obj->co;
+
+=head2 CHECK-OUT FILE
+
+Set class variables and create 'RCS' object.
+Set bin directory where RCS programs (e.g. rcs, ci, co) reside. The
+default is '/usr/local/bin'. This sets the bin directory for all objects.
+
+ use Rcs;
+ Rcs->bindir('/usr/bin');
+ Rcs->quiet(0); # turn off quiet mode
+ $obj = Rcs->new;
+
+Set information regarding RCS object. This information includes name of
+working file, directory of working file ('.' by default), and RCS archive
+directory ('./RCS' by default).
+
+ $obj->file('cornholio.pl');
+
+ # Set RCS archive directory, is './RCS' by default
+ $obj->rcsdir("./project_tree/archive");
+
+ # Set working directory, is '.' by default
+ $obj->workdir("./project_tree/src");
+
+Check out file read-only:
+
+ $obj->co;
+
+or check out and lock file:
+
+ $obj->co('-l');
+
+=head2 RCSDIFF
+
+Method B<rcsdiff> does an diff between revisions.
+
+ $obj = Rcs->new;
+ $obj->bindir('/usr/bin');
+
+ $obj->rcsdir("./project_tree/archive");
+ $obj->workdir("./project_tree/src");
+ $obj->file("cornholio.pl");
+
+ print "Diff of current working file\n";
+ if ($obj->rcsdiff) { # scalar context
+ print $obj->rcsdiff; # list context
+ }
+ else {
+ print "Versions are Equal\n";
+ }
+
+ print "\n\nDiff of revisions 1.2 and 1.1\n";
+ print $obj->rcsdiff('-r1.2', '-r1.1');
+
+=head2 RCSCLEAN
+
+Method B<rcsclean> will remove an unlocked working file.
+
+ use Rcs;
+ Rcs->bindir('/usr/bin');
+ Rcs->quiet(0); # turn off quiet mode
+ $obj = Rcs->new;
+
+ $obj->rcsdir("./project_tree/archive");
+ $obj->workdir("./project_tree/src");
+ $obj->file("cornholio.pl");
+
+ print "Quiet mode NOT set\n" unless Rcs->quiet;
+
+ $obj->rcsclean;
+
+=head1 AUTHOR
+
+Craig Freter, E<lt>F<craig@freter.com>E<gt>
+
+=head1 CONTRIBUTORS
+
+David Green, E<lt>F<greendjf@cvhp152.gpt.co.uk>E<gt>
+
+ David Green contributed the B<dates> method.
+
+Jamie O'Shaughnessy, E<lt>F<jamie@thanatar.demon.co.uk>E<gt>
+
+ Contributed NT port.
+ Contributed methods B<daterev>, B<symrev>, and B<symbols>.
+
+=head1 COPYRIGHT
+
+Copyright (C) 1997,1998 Craig Freter. All rights reserved.
+This program is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
+
+=cut
+
diff --git a/cpan/dist/Rcs/examples/project/src/testfile b/cpan/dist/Rcs/examples/project/src/testfile
new file mode 100644
index 00000000..59d119de
--- /dev/null
+++ b/cpan/dist/Rcs/examples/project/src/testfile
@@ -0,0 +1,2 @@
+3.14
+hear again
diff --git a/cpan/dist/Rcs/examples/rcsclean.pl b/cpan/dist/Rcs/examples/rcsclean.pl
new file mode 100755
index 00000000..957cfc73
--- /dev/null
+++ b/cpan/dist/Rcs/examples/rcsclean.pl
@@ -0,0 +1,18 @@
+#!/usr/local/bin/perl -w
+#------------------------------------------
+# rcsclean utility
+#------------------------------------------
+use strict;
+use Rcs;
+
+Rcs->quiet(0); # turn off quiet mode
+Rcs->bindir('/usr/bin');
+my $obj = Rcs->new;
+
+print "Quiet mode NOT set\n" unless Rcs->quiet;
+
+$obj->rcsdir("./project/RCS");
+$obj->workdir("./project/src");
+$obj->file("testfile");
+
+$obj->rcsclean;
diff --git a/cpan/dist/Rcs/examples/rcsdiff.pl b/cpan/dist/Rcs/examples/rcsdiff.pl
new file mode 100755
index 00000000..7bd343f9
--- /dev/null
+++ b/cpan/dist/Rcs/examples/rcsdiff.pl
@@ -0,0 +1,27 @@
+#!/usr/local/bin/perl -w
+#------------------------------------------
+# Use rcsdiff utility.
+#------------------------------------------
+use strict;
+use Rcs;
+
+Rcs->quiet(1);
+
+my $obj = Rcs->new;
+$obj->bindir('/usr/bin');
+print "Quiet mode set\n" if Rcs->quiet;
+
+$obj->rcsdir("./project/RCS");
+$obj->workdir("./project/src");
+$obj->file("testfile");
+
+print "Diff of current working file\n";
+if ($obj->rcsdiff) { # scalar context
+ print $obj->rcsdiff; # list context
+}
+else {
+ print "Versions are Equal\n";
+}
+
+print "\n\nDiff of revisions 1.2 and 1.1\n";
+print $obj->rcsdiff('-r1.2', '-r1.1');
diff --git a/cpan/dist/Rcs/examples/revdate.pl b/cpan/dist/Rcs/examples/revdate.pl
new file mode 100755
index 00000000..b20669f8
--- /dev/null
+++ b/cpan/dist/Rcs/examples/revdate.pl
@@ -0,0 +1,28 @@
+#!/usr/local/bin/perl -w
+#------------------------------------------
+# Get revision date
+#------------------------------------------
+use strict;
+use Rcs;
+
+Rcs->bindir('/usr/bin');
+my $obj = Rcs->new;
+
+$obj->rcsdir("./project/RCS");
+$obj->workdir("./project/src");
+$obj->file("testfile");
+my $revision = shift || $obj->head;
+die "Revision $revision does not exist\n"
+ unless grep /^$revision$/, $obj->revisions;
+
+# scalar mode
+my $date_num = $obj->revdate($revision);
+print "Revision : Date number = $revision : $date_num\n";
+
+my $date_str = localtime($date_num);
+print "Revision : Date string = $revision : $date_str\n";
+
+# list mode
+my @list_date = $obj->revdate($revision);
+print "Revision : Date array = $revision : @list_date\n";
+
diff --git a/cpan/dist/Rcs/examples/rlog.pl b/cpan/dist/Rcs/examples/rlog.pl
new file mode 100755
index 00000000..14b66886
--- /dev/null
+++ b/cpan/dist/Rcs/examples/rlog.pl
@@ -0,0 +1,20 @@
+#!/usr/local/bin/perl -w
+#------------------------------------------
+# Use rlog utility.
+#------------------------------------------
+use strict;
+use Rcs;
+
+my $obj = Rcs->new;
+
+# call quiet and bindir as objest methods
+$obj->quiet(1);
+$obj->bindir('/usr/bin');
+
+print "Quiet mode set\n" if Rcs->quiet;
+
+$obj->rcsdir("./project/RCS");
+$obj->workdir("./project/src");
+$obj->file("testfile");
+
+print $obj->rlog;
diff --git a/cpan/dist/Rcs/examples/symbols.pl b/cpan/dist/Rcs/examples/symbols.pl
new file mode 100755
index 00000000..ffcafc32
--- /dev/null
+++ b/cpan/dist/Rcs/examples/symbols.pl
@@ -0,0 +1,23 @@
+#!/usr/local/bin/perl -w
+#------------------------------------------
+# Test symrev method
+#------------------------------------------
+use strict;
+use Rcs;
+
+Rcs->bindir('/usr/bin');
+my $obj = Rcs->new;
+
+$obj->rcsdir("./project/RCS");
+$obj->workdir("./project/src");
+$obj->file("testfile");
+
+my %symbols = $obj->symbols;
+my $sym;
+foreach $sym (keys %symbols) {
+ my $rev = $symbols{$sym};
+ print "Symbol : Revision = $sym : $rev\n";
+}
+
+my @syms = keys %symbols;
+print "@syms\n";
diff --git a/cpan/dist/Rcs/examples/symrev.pl b/cpan/dist/Rcs/examples/symrev.pl
new file mode 100755
index 00000000..20af459c
--- /dev/null
+++ b/cpan/dist/Rcs/examples/symrev.pl
@@ -0,0 +1,27 @@
+#!/usr/local/bin/perl -w
+#------------------------------------------
+# Test symrev method
+#------------------------------------------
+use strict;
+use Rcs;
+
+#Rcs->bindir('/usr/bin');
+my $obj = Rcs->new;
+
+$obj->rcsdir("./project/RCS");
+$obj->workdir("./project/src");
+$obj->file("testfile");
+(my $symbol = shift) or die "Usage: $0 symbol\n";
+
+# scalar mode
+print "Scalar mode:\n";
+my $revision = $obj->symrev($symbol);
+print "Symbol : Revision = $symbol : $revision\n";
+
+
+# list mode
+print "\nList mode:\n";
+my %symbols = $obj->symrev($symbol);
+foreach (keys %symbols) {
+ print "Symbol : Revision = $_ : $symbols{$_}\n";
+}
diff --git a/cpan/dist/Rcs/examples/test1.pl b/cpan/dist/Rcs/examples/test1.pl
new file mode 100755
index 00000000..209f9930
--- /dev/null
+++ b/cpan/dist/Rcs/examples/test1.pl
@@ -0,0 +1,14 @@
+#!/usr/local/bin/perl -w
+
+use strict;
+use lib '.';
+use Rcs;
+
+my $p = new Rcs();
+$p->workdir("foo");
+
+my $n = new Rcs();
+$n->workdir("bar");
+
+print $p->workdir, "\n";
+print $n->workdir, "\n";
diff --git a/cpan/dist/Rcs/examples/test2.pl b/cpan/dist/Rcs/examples/test2.pl
new file mode 100755
index 00000000..4992cc9b
--- /dev/null
+++ b/cpan/dist/Rcs/examples/test2.pl
@@ -0,0 +1,15 @@
+#!/usr/local/bin/perl -w
+
+use strict;
+use lib '.';
+use Rcs;
+
+Rcs->workdir("foo");
+
+my $p = new Rcs();
+my $n = new Rcs();
+
+Rcs->workdir("bar");
+
+print $p->workdir, "\n";
+print $n->workdir, "\n";
diff --git a/cpan/dist/Rcs/examples/test3.pl b/cpan/dist/Rcs/examples/test3.pl
new file mode 100755
index 00000000..4d0c36ed
--- /dev/null
+++ b/cpan/dist/Rcs/examples/test3.pl
@@ -0,0 +1,17 @@
+#!/usr/local/bin/perl -w
+
+use strict;
+use lib '.';
+use Rcs;
+
+my $p = new Rcs();
+my $n = new Rcs();
+
+my $m = new Rcs();
+$m->workdir("foo");
+
+Rcs->workdir("bar");
+
+print $p->workdir, "\n";
+print $n->workdir, "\n";
+print $m->workdir, "\n";
diff --git a/cpan/dist/Rcs/examples/type b/cpan/dist/Rcs/examples/type
new file mode 100644
index 00000000..29a4ee24
--- /dev/null
+++ b/cpan/dist/Rcs/examples/type
@@ -0,0 +1,13 @@
+Check-in
+./project/RCS/testfile,v <-- ./project/src/testfile
+new revision: 1.4; previous revision: 1.3
+enter log message, terminated with single '.' or end of file:
+>> @test multi-line comment@@@@@@
+>> @
+>> @@
+>> @@@
+>> @@@@
+>> multiline comment
+>> @
+>> .
+done
diff --git a/cpan/dist/Rcs/examples/unlock.pl b/cpan/dist/Rcs/examples/unlock.pl
new file mode 100755
index 00000000..8d60137c
--- /dev/null
+++ b/cpan/dist/Rcs/examples/unlock.pl
@@ -0,0 +1,20 @@
+#!/usr/local/bin/perl -w
+#------------------------------------------
+# Unlock RCS file
+#------------------------------------------
+use strict;
+use Rcs;
+
+Rcs->bindir('/usr/bin');
+Rcs->quiet(0);
+my $obj = Rcs->new;
+
+$obj->rcsdir("./project/RCS");
+$obj->workdir("./project/src");
+$obj->file("testfile");
+my $revision = shift || $obj->head;
+die "Revision $revision does not exist\n"
+ unless grep /^$revision$/, $obj->revisions;
+
+$obj->rcs("-u${revision}");
+
diff --git a/cpan/dist/Rcs/test.pl b/cpan/dist/Rcs/test.pl
new file mode 100644
index 00000000..41c2ffc9
--- /dev/null
+++ b/cpan/dist/Rcs/test.pl
@@ -0,0 +1,20 @@
+# Before `make install' is performed this script should be runnable with
+# `make test'. After `make install' it should work as `perl test.pl'
+
+######################### We start with some black magic to print on failure.
+
+# Change 1..1 below to 1..last_test_to_print .
+# (It may become useful if the test is moved to ./t subdirectory.)
+
+BEGIN { $| = 1; print "1..1\n"; }
+END {print "not ok 1\n" unless $loaded;}
+use Rcs;
+$loaded = 1;
+print "ok 1\n";
+
+######################### End of black magic.
+
+# Insert your test code below (better if it prints "ok 13"
+# (correspondingly "not ok 13") depending on the success of chunk 13
+# of the test code):
+
diff --git a/cpan/dist/VCS-CVS/CVS.pm b/cpan/dist/VCS-CVS/CVS.pm
new file mode 100644
index 00000000..3204e1b3
--- /dev/null
+++ b/cpan/dist/VCS-CVS/CVS.pm
@@ -0,0 +1,1474 @@
+package VCS::CVS;
+
+# Name:
+# VCS::CVS.
+#
+# Documentation:
+# POD-style documentation is at the end. Extract it with pod2html.
+#
+# Tabs:
+# 4 spaces || die.
+#
+# --------------------------------------------------------------------------
+
+use strict;
+no strict 'refs';
+
+use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
+
+use Carp;
+use Cwd;
+use File::Find;
+use File::Path;
+
+require Exporter;
+
+@ISA = qw(Exporter);
+
+# Items to export into callers namespace by default. Note: do not export
+# names by default without a very good reason. Use EXPORT_OK instead.
+# Do not simply export all your public functions/methods/constants.
+
+@EXPORT = qw();
+
+@EXPORT_OK = qw();
+
+$VERSION = '2.00';
+
+# Preloaded methods go here.
+# --------------------------------------------------------------------------
+# Add an existing directory to the project.
+# $dir can be a full path, or relative to the CWD.
+
+sub addDirectory
+{
+ my($self, $dir, $subDir, $message) = @_;
+
+ # Preserve the caller's current working directory.
+ my($cwd) = cwd();
+ chdir($dir) || croak("Can't chdir($dir): \nFailure: $!");
+
+ # CVS options:
+ # -Q Really quiet.
+ # -m message Use this log message.
+ # $subDir Add this directory.
+
+ # Warning: Do not try to combine these lines under any circumstances...
+ # Perl can't handle null list elements in a call to system.
+ my(@args) = ('cvs');
+ push(@args, '-Q') if (! $self -> {'verbose'});
+ push(@args, 'add');
+
+ if ($message)
+ {
+ $message = '"' . $message . '"' if ($message !~ /^".*"$/);
+ push(@args, '-m', $message);
+ }
+
+ push(@args, $subDir);
+
+ $self -> runOrCroak(@args);
+
+ chdir($cwd) || croak("Can't chdir($cwd): $!");
+
+} # End of addDirectory.
+
+# --------------------------------------------------------------------------
+# Add an existing file to the project.
+# $dir can be a full path, or relative to the CWD.
+
+sub addFile
+{
+ my($self, $dir, $file, $message) = @_;
+
+ # Preserve the caller's current working directory.
+ my($cwd) = cwd();
+ chdir($dir) || croak("Can't chdir($dir): \nFailure: $!");
+
+ # CVS options:
+ # -Q Really quiet.
+ # -m message Use this log message.
+ # $file Add this file.
+
+ # Warning: Do not try to combine these lines under any circumstances...
+ # Perl can't handle null list elements in a call to system.
+ my(@args) = ('cvs');
+ push(@args, '-Q') if (! $self -> {'verbose'});
+ push(@args, 'add');
+
+ if ($message)
+ {
+ $message = '"' . $message . '"' if ($message !~ /^".*"$/);
+ push(@args, '-m', $message);
+ }
+
+ push(@args, $file);
+
+ $self -> runOrCroak(@args);
+
+ $self -> commit($message);
+
+ chdir($cwd) || croak("Can't chdir($cwd): $!");
+
+} # End of addFile.
+
+# --------------------------------------------------------------------------
+# Prepare & perform 'cvs checkout'.
+# You call checkOut, and it calls _checkOutDontCallMe.
+# $readOnly Interpretation
+# 0 Check out files as read-write
+# 1 Check out files as read-only
+# $tag Interpretation
+# Null Do not call upToDate; ie check out repository as is
+# ! Null Call upToDate; Croak if repository is not up-to-date
+# If you called new with $raw == 1, your tag is passed as is to CVS.
+# If you called new with $raw == 0, your tag is assumed to be of the
+# form release_1.23, and is converted to CVS's form release_1_23.
+# $dir can be a full path, or relative to the CWD.
+
+sub checkOut
+{
+ my($self, $readOnly, $tag, $dir) = @_;
+
+ $tag =~ s/([-a-zA-Z]+_\d\d?)\.(\d\d)/$1_$2/ if (! $self -> {'raw'});
+
+ $self -> _validateObject($self -> {'project'}, 'modules', 0);
+ $self -> _validateObject($tag, 'val-tags', 0);
+
+ croak("Failure: Move directory $dir out of the way") if (-d $dir);
+
+ # Ensure the repository is up-to-date.
+ croak("Failure: The repository is not up-to-date. Run 'cvs commit' or 'cvs update'")
+ if ($tag && (! $self -> upToDate() ) );
+
+ # Zap previous copy of work directory.
+ rmtree($dir, $self -> {'verbose'});
+
+ # Checkout a current copy of the project.
+ $self -> _checkOutDontCallMe($readOnly, $tag, $dir);
+
+} # End of checkOut.
+
+# --------------------------------------------------------------------------
+# Commit changes.
+# Called as appropriate by addFile, removeFile and removeDirectory,
+# so you don't need to call it.
+
+sub commit
+{
+ my($self, $message) = @_;
+
+ # CVS options:
+ # -Q Really quiet.
+ # -m message Use this log message.
+
+ # Warning: Do not try to combine these lines under any circumstances...
+ # Perl can't handle null list elements in a call to system.
+ my(@args) = ('cvs');
+ push(@args, '-Q') if (! $self -> {'verbose'});
+ push(@args, 'commit');
+
+ if ($message)
+ {
+ $message = '"' . $message . '"' if ($message !~ /^".*"$/);
+ push(@args, '-m', $message);
+ }
+
+ $self -> runOrCroak(@args);
+
+} # End of commit.
+
+# --------------------------------------------------------------------------
+# Create a repository, using the current $CVSROOT.
+
+sub createRepository
+{
+ my($self) = @_;
+
+ croak("Failure: Move directory $ENV{'CVSROOT'} out of the way") if (-d $ENV{'CVSROOT'});
+
+ # Create the repository and its files.
+ $self -> _mkpathOrCroak($ENV{'CVSROOT'});
+ $self -> _mkpathOrCroak("$ENV{'CVSROOT'}/CVSROOT");
+
+ # Create the modules file.
+ my(@args) = ();
+ push(@args, "CVSROOT\t\tCVSROOT");
+ push(@args, "modules\t\tCVSROOT\tmodules");
+ push(@args, "$self->{'project'}\t\t$self->{'project'}");
+
+ my($file) = "$ENV{'CVSROOT'}/CVSROOT/modules";
+ open(OUT, "> $file") || croak("Can't open($file): \nFailure: $!");
+ print OUT join("\n", @args), "\n";
+ close(OUT);
+
+ $file = "$ENV{'CVSROOT'}/CVSROOT/val-tags";
+ open(OUT, "> $file") || croak("Can't open($file): \nFailure: $!");
+ # Write nothing.
+ close(OUT);
+
+ if ($self -> {'history'})
+ {
+ $file = "$ENV{'CVSROOT'}/CVSROOT/history";
+ open(OUT, "> $file") || croak("Can't open($file): \nFailure: $!");
+ # Write nothing.
+ close(OUT);
+ }
+
+} # End of createRepository.
+
+# --------------------------------------------------------------------------
+# Return a reference to a list of tags.
+# See also: the $raw option to new().
+
+sub getTags
+{
+ my($self) = @_;
+
+ my($line) = [];
+
+ if (-e "$ENV{'CVSROOT'}/CVSROOT/val-tags")
+ {
+ $line = $self -> _readFile("$ENV{'CVSROOT'}/CVSROOT/val-tags");
+
+ for (@$line)
+ {
+ $_ = (split)[0];
+
+ # Convert tag_1_23 into tag_1.23, if requested.
+ s/([-a-zA-Z]+_\d\d?)_(\d\d)/$1\.$2/ if (! $self -> {'raw'});
+ }
+
+ }
+
+ $line;
+
+} # End of getTags.
+
+# --------------------------------------------------------------------------
+# Run cvs history [-options].
+# Return a reference to a list of lines.
+#
+# The default option is -c.
+
+sub history
+{
+ my($self, $optionRef) = @_;
+
+ # Preserve the caller's current working directory.
+ # cvs status only works on the whole repository when run from your project dir
+ # (assuming, of course, you've checked out into your home directory...).
+ my($cwd) = cwd();
+ chdir("$ENV{'HOME'}/$self->{'project'}") ||
+ croak("Can't chdir($ENV{'HOME'}/$self->{'project'}): $!");
+
+ # CVS history options:
+ # -c Report commits, ie -xARM.
+
+ if (ref($optionRef) ne 'HASH')
+ {
+ $optionRef = {'-c' => ''};
+ }
+
+ my(@args) = ('cvs');
+ push(@args, 'history');
+ push(@args, join(' ', %$optionRef) );
+ @args = `@args`;
+ chomp(@args);
+
+ chdir($cwd) || croak("Can't chdir($cwd): $!");
+
+ \@args;
+
+} # End of history.
+
+# --------------------------------------------------------------------------
+# These are the options in the anonymous hash of parameters you pass in to 'new'.
+#
+# 'project'
+# 'killerApp' The name of the project. No default
+#
+# 'history'
+# 0 Do not create $CVSROOT/CVSROOT/history when createRepository() is called. Default
+# 1 Create $CVSROOT/CVSROOT/history, which initiates 'cvs history' stuff
+#
+# 'permissions'
+# 0775 Unix-specific. Default. Do not use '0775'
+#
+# 'raw'
+# 0 Convert tags from CVS format to real format. Eg: release_1.23. Default
+# 1 Set/Get tags in raw CVS format. Eg: release_1_23
+#
+# 'verbose'
+# 0 Run quietly
+# 1 Report progress. Default
+
+sub new
+{
+ my($class, $optionRef) = @_;
+ $class = ref($class) || $class;
+ my($self) = (ref($optionRef) eq 'HASH') ? $optionRef : {};
+
+ my(%default) =
+ (
+ 'history' => 0,
+ 'permissions' => 0775, # But not '0775'!
+ 'project' => '',
+ 'raw' => 0,
+ 'verbose' => 1,
+ );
+
+ my($option);
+
+ for $option (keys(%default) )
+ {
+ $self -> {$option} = $default{$option} if (! defined($self -> {$option}) );
+ }
+
+ $ENV{'HOME'} = '' if (! defined($ENV{'HOME'}) );
+ $ENV{'CVSROOT'} = '' if (! defined($ENV{'CVSROOT'}) );
+
+ croak("Failure: No project name specified") if (! $self -> {'project'});
+ croak("Failure: Env. var HOME not set") if (! $ENV{'HOME'});
+ croak("Failure: Env. var CVSROOT not set") if (! $ENV{'CVSROOT'});
+
+ return bless $self, $class;
+
+} # End of new.
+
+# --------------------------------------------------------------------------
+# Import an existing directory structure. But, (sub) import is a reserved word.
+# Use this to populate a repository for the first time.
+# The value used for $vendorTag is not important; CVS discards it.
+# The value used to $releaseTag is important; CVS discards it (why?) but I
+# force it to be the first tag in $CVSROOT/CVSROOT/val-tags. Thus you
+# should supply a meaningful value. Thus 'release_0_00' is strongly, repeat
+# strongly, recommended.
+# If you called new with $raw == 1, $releaseTag is passed as is to CVS.
+# If you called new with $raw == 0, $releaseTag is assumed to be of the
+# form release_1.23, and is converted to CVS's form release_1_23.
+
+# $sourceDir can be a full path, or relative to the CWD.
+
+sub populate
+{
+ my($self, $sourceDir, $vendorTag, $releaseTag, $message) = @_;
+
+ $vendorTag = 'vendorTag' if ( ($#_ < 2) || (length($_[2]) == 0) );
+ $releaseTag = 'release_0_00' if ( ($#_ < 3) || (length($_[3]) == 0) );
+ $message = 'Initial version' if ($#_ < 4);
+
+ $releaseTag =~ s/([-a-zA-Z]+_\d\d?)\.(\d\d)/$1_$2/ if (! $self -> {'raw'});
+
+ # Preserve the caller's current working directory.
+ my($cwd) = cwd();
+ chdir($sourceDir) || croak("Can't chdir($sourceDir): \nFailure: $!");
+
+ # CVS options:
+ # -Q Really quiet.
+ # -m message Use this log message.
+
+ # Warning: Do not try to combine these lines under any circumstances...
+ # Perl can't handle null list elements in a call to system.
+ my(@args) = ('cvs');
+ push(@args, '-Q') if (! $self -> {'verbose'});
+ push(@args, 'import');
+
+ if ($message)
+ {
+ $message = '"' . $message . '"' if ($message !~ /^".*"$/);
+ push(@args, '-m', $message);
+ }
+
+ push(@args, $self -> {'project'}, $vendorTag, $releaseTag);
+
+ $self -> runOrCroak(@args);
+
+ chdir($cwd) || croak("Can't chdir($cwd): $!");
+
+ # Compensate for yet another CVS bug.
+ $self -> _fixTag($releaseTag);
+
+} # End of populate.
+
+# --------------------------------------------------------------------------
+# Remove a directory from the project.
+# This deletes the directory (and all its files) from your working copy
+# of the repository, as well as deleting them from the repository.
+# Warning: $dir will have $CVSROOT and $HOME prepended by this code.
+# Ie: $dir starts from - but excludes - your home directory
+# (assuming, of course, you've checked out into your home directory...).
+# You can't remove the current directory, or a parent thereof.
+
+sub removeDirectory
+{
+ my($self, $dir) = @_;
+
+ my($cvsDir) = "$ENV{'CVSROOT'}/$dir/";
+ my($workDir) = "$ENV{'HOME'}/$dir/";
+
+ # Preserve the caller's current working directory.
+ my($cwd) = cwd();
+
+ # Move into the work directory.
+ chdir($workDir) || croak("Can't chdir($workDir): \nFailure: $!");
+ my($thisCwd) = cwd();
+
+ # Sanity check.
+ croak("Failure: You can't remove the current directory, or a parent") if ($cwd =~ /^$thisCwd/);
+
+ # Ensure the repository is up-to-date.
+ croak("Failure: The repository is not up-to-date. Run 'cvs commit' or 'cvs update'")
+ if (! $self -> upToDate() );
+
+ # Read the CVS entries.
+ my($cvsEntries) = 'CVS/Entries';
+ my($entry) = $self -> _readFile($cvsEntries);
+
+ # Remove each file, using CVS.
+ for (@$entry)
+ {
+ next if (/^D/);
+
+ my($file);
+
+ $file = $1 if (/^\/(.+?)\//);
+
+ $self -> removeFile($workDir, $file, 'Whole directory removed');
+ }
+
+ $self -> commit('Whole directory removed');
+
+ # Move up, and remove the directory.
+ chdir('..') || croak("Can't chdir('..'): \nFailure: $!");
+ my($directory) = $workDir;
+ my($index) = rindex($directory, '/', (length($directory) - 2) );
+ substr($directory, 0, ($index + 1) ) = '';
+ rmtree($directory, $self -> {'verbose'});
+
+ # Edit the CVS entries file to remove the dir.
+ if (-f $cvsEntries)
+ {
+ $entry = $self -> _readFile($cvsEntries);
+ @$entry = grep(! /^D\/$directory\//, @$entry);
+ open(OUT, "> $cvsEntries") || croak("Can't open $cvsEntries: \nFailure: $!");
+ print OUT join("\n", @$entry), "\n";
+ close(OUT);
+ }
+
+ # Remove the directory from CVS.
+ rmtree($cvsDir, $self -> {'verbose'});
+
+ # Remove the directory from the modules list.
+ if ($dir !~ /\//)
+ {
+ $cvsEntries = "$ENV{'CVSROOT'}/CVSROOT/modules";
+ $entry = $self -> _readFile($cvsEntries);
+
+ my($i);
+
+ for ($i = 0; $i <= $#{$entry}; $i++)
+ {
+ my(@field) = split(/\s+/, $$entry[$i]);
+ splice(@$entry, $i, 1) if ($field[1] =~ /^$dir$/);
+ }
+
+ open(OUT, "> $cvsEntries") || croak("Can't open $cvsEntries: \nFailure: $!");
+ print OUT join("\n", @$entry), "\n";
+ close(OUT);
+ }
+
+ chdir($cwd) || croak("Can't chdir($cwd): $!");
+
+} # End of removeDirectory.
+
+# --------------------------------------------------------------------------
+# Remove a file from the project.
+# This deletes the file from your working copy of the repository,
+# as well as deleting it from the repository.
+# $dir can be a full path, or relative to the CWD.
+# $file is relative to $dir.
+
+sub removeFile
+{
+ my($self, $dir, $file, $message) = @_;
+
+ # Preserve the caller's current working directory.
+ my($cwd) = cwd();
+ chdir($dir) || croak("Can't chdir($dir): \nFailure: $!");
+
+ unlink($file) || croak("Can't unlink($file): $!");
+
+ # CVS options:
+ # -Q Really quiet.
+ # -f Remove the file first.
+ # -l Do not recurse.
+ # $file Checkout this module.
+
+ my(@args) = ('cvs');
+ push(@args, '-Q') if (! $self -> {'verbose'});
+ push(@args, 'remove', '-f', '-l', $file);
+
+ $self -> runOrCroak(@args);
+
+ $self -> commit($message);
+
+ chdir($cwd) || croak("Can't chdir($cwd): $!");
+
+} # End of removeFile.
+
+# --------------------------------------------------------------------------
+# The standard way to run a system command and report on the result.
+
+sub runOrCroak
+{
+ my($self, @args) = @_;
+
+ my($result) = 0xffff & system(@args);
+
+ print "Command: @args\n";
+
+ if ($result == 0)
+ {
+ print 'Success. ';
+ }
+ elsif ($result == 0xff00)
+ {
+ print "Failure: $!. ";
+ }
+ elsif ($result > 0x80)
+ {
+ $result >>= 8;
+ print "Exit status: $result. ";
+ }
+ else
+ {
+ if ($result & 0x80)
+ {
+ $result &= ~0x80;
+ print 'Coredump from ';
+ }
+
+ print "Signal $result. ";
+ }
+
+ printf("Result: %#04x\n", $result);
+
+ croak("Failure: Can't run '@args'") if ($result);
+
+} # End of runOrCroak.
+
+# --------------------------------------------------------------------------
+# Tag the repository.
+# You call setTag, and it calls _setTag.
+# If you called new with $raw == 1, your tag is passed as is to CVS.
+# If you called new with $raw == 0, your tag is assumed to be of the
+# form release_1.23, and is converted to CVS's form release_1_23.
+
+sub setTag
+{
+ my($self, $tag) = @_;
+
+ $tag =~ s/([-a-zA-Z]+_\d\d?)\.(\d\d)/$1_$2/ if (! $self -> {'raw'});
+
+ $self -> _validateObject($self -> {'project'}, 'modules', 0);
+ $self -> _validateObject($tag, 'val-tags', 1);
+
+ croak("Failure: The repository is not up-to-date. Run 'cvs commit' or 'cvs update'")
+ if ($self -> upToDate() == 0);
+
+ $self -> _setTag($tag);
+
+} # End of setTag.
+
+# --------------------------------------------------------------------------
+# Run cvs status.
+# Return a reference to a list of lines.
+# Only called by upToDate(), but you may call it.
+
+sub status
+{
+ my($self) = @_;
+
+ # Preserve the caller's current working directory.
+ # cvs status only works on the whole repository when run from your project dir
+ # (assuming, of course, you've checked out into your home directory...).
+ my($cwd) = cwd();
+ chdir("$ENV{'HOME'}/$self->{'project'}") ||
+ croak("Can't chdir($ENV{'HOME'}/$self->{'project'}): $!");
+
+ # CVS options:
+ # -Q Really quiet.
+
+ my(@args) = ('cvs');
+ push(@args, '-Q') if (! $self -> {'verbose'});
+ push(@args, 'status');
+ @args = `@args`;
+ chomp(@args);
+
+ chdir($cwd) || croak("Can't chdir($cwd): $!");
+
+ \@args;
+
+} # End of status.
+
+# --------------------------------------------------------------------------
+# Delete all CVS directories and files from a copy of the repository.
+
+sub stripCVSDirs
+{
+ my($self, $dir) = @_;
+
+ # Preserve the caller's current working directory.
+ my($cwd) = cwd();
+ chdir($dir) || croak("Can't chdir($dir): $!");
+
+ my(%dirStack);
+
+ find
+ (
+ sub
+ {
+ $dirStack{$File::Find::dir} = 1 if ($File::Find::dir =~ /\/CVS$/);
+ },
+ cwd()
+ );
+
+ for (keys(%dirStack) )
+ {
+ rmtree($_, $self -> {'verbose'});
+ }
+
+ chdir($cwd) || croak("Can't chdir($cwd): $!");
+
+} # End of stripCVSDirs.
+
+# --------------------------------------------------------------------------
+# Run cvs -q [-n] update.
+# Return a reference to a list of lines.
+# Each line will start with one of [UARMC?], as per the CVS docs.
+#
+# Parameters Interpretation
+# $n 0 -> Do not add -n to the cvs update command
+# 1 -> Add -n to the command
+
+sub update
+{
+ my($self, $n) = @_;
+
+ $n = 0 if (! defined($n) );
+
+ # Preserve the caller's current working directory.
+ # cvs status only works on the whole repository when run from your project dir
+ # (assuming, of course, you've checked out into your home directory...).
+ my($cwd) = cwd();
+ chdir("$ENV{'HOME'}/$self->{'project'}") ||
+ croak("Can't chdir($ENV{'HOME'}/$self->{'project'}): $!");
+
+ # CVS options:
+ # -q Quiet
+ # -n Do not change any files
+
+ my(@args) = ('cvs');
+ push(@args, '-q') if (! $self -> {'verbose'});
+ push(@args, '-n') if ($n);
+ push(@args, 'update');
+ @args = `@args`;
+ chomp(@args);
+
+ chdir($cwd) || croak("Can't chdir($cwd): $!");
+
+ \@args;
+
+} # End of update.
+
+# --------------------------------------------------------------------------
+# Return Interpretation
+# 0 Repository not up-to-date.
+# 1 Up-to-date.
+
+sub upToDate
+{
+ my($self) = @_;
+
+ # Get the status of the repository.
+ my($status) = $self -> status();
+ @$status = grep(/Status/ && ! /Up-to-date/, @$status);
+ my($result) = 1; # Up-to-date.
+ $result = 0 if ($#{$status} >= 0); # Not, because log contains something.
+
+ $result;
+
+} # End of upToDate.
+
+# --------------------------------------------------------------------------
+# Checkout a current copy of the project.
+# You call checkOut, and it calls this.
+
+sub _checkOutDontCallMe
+{
+ my($self, $readOnly, $tag, $dir) = @_;
+
+ # CVS options:
+ # -Q Really quiet.
+ # -r Read-only. Make the new working files read-only.
+ # -d$dir Use $dir, not $project, as the directory name.
+ # -r <tag> Check out files tagged with <tag>. Optional.
+ #
+ # $project Checkout this module.
+
+ # CVS bug. Remove trailing '/', if any.
+ $dir = $1 if ($dir =~ /^(.+)\/$/);
+
+ # Warning: Do not try to combine these lines under any circumstances...
+ # Perl can't handle null list elements in a call to system.
+ my(@args) = ('cvs');
+ push(@args, '-Q') if (! $self -> {'verbose'});
+ push(@args, '-r') if ($readOnly);
+ push(@args, 'checkout', '-A', '-P', "-d$dir");
+ push(@args, '-r', $tag) if ($tag);
+ push(@args, $self -> {'project'});
+
+ $self -> runOrCroak(@args);
+
+} # End of _checkOutDontCallMe.
+
+# --------------------------------------------------------------------------
+# Fix a tag CVS failed to add.
+# Warning: $tag must be in CVS format. Eg: release_1_23, not release_1.23.
+
+sub _fixTag
+{
+ my($self, $tag) = @_;
+
+ my($file) = "$ENV{'CVSROOT'}/CVSROOT/val-tags";
+
+ open(INX, $file) || croak("Can't open($file): \nFailure: $!");
+
+ my($found) = 0;
+
+ while (<INX>)
+ {
+ $found = 1 if (/^$tag/);
+ }
+
+ close(INX);
+
+ if (! $found)
+ {
+ print "Warning: CVS bug. Tag $tag not in file $file\n" if ($self -> {'verbose'});
+ print "Fixing... " if ($self -> {'verbose'});
+
+ open(OUT, ">> $file") || croak("Can't open(>>$file): \nFailure: $!");
+ print OUT "$tag y\n";
+ close(OUT);
+
+ print "Success\n" if ($self -> {'verbose'});
+ }
+
+} # End of _fixTag.
+
+# --------------------------------------------------------------------------
+
+sub _mkpathOrCroak
+{
+ my($self, $dir) = @_;
+
+ my($result) = mkpath($dir, $self -> {'verbose'}, $self -> {'permissions'});
+
+ croak("Can't mkpath($dir, $self->{'verbose'}, $self->{'permissions'}): \nFailure: $!")
+ if ( (! $result) && ($! !~ /No such file/) );
+
+} # End of _mkpathOrCroak.
+
+# --------------------------------------------------------------------------
+# Return a reference to a list of lines.
+
+sub _readFile
+{
+ my($self, $file) = @_;
+
+ open(INX, $file) || croak("Can't open($file): $!");
+ my(@line) = <INX>;
+ close(INX);
+ chomp(@line);
+
+ \@line;
+
+} # end of _readFile.
+
+# --------------------------------------------------------------------------
+# Tag the current version of the project.
+# Warning: $tag must be in CVS format. Eg: release_1_23, not release_1.23.
+# You call setTag and it calls this.
+
+sub _setTag
+{
+ my($self, $tag) = @_;
+
+ # Preserve the caller's current working directory.
+ # cvs tag only works on the whole repository when run from your project dir
+ # (assuming, of course, you've checked out into your home directory...).
+ my($cwd) = cwd();
+ chdir($ENV{'HOME'}) || croak("Can't chdir($ENV{'HOME'}): $!");
+
+ # CVS options:
+ # -Q Really quiet.
+ # -r <tag> Tag files with <tag>.
+ # $project Tag this module.
+
+ # Warning: Do not try to combine these lines under any circumstances...
+ # Perl can't handle null list elements in a call to system.
+ my(@args) = ('cvs');
+ push(@args, '-Q') if (! $self -> {'verbose'});
+ push(@args, 'tag', $tag, $self -> {'project'});
+
+ $self -> runOrCroak(@args);
+
+ chdir($cwd) || croak("Can't chdir($cwd): $!");
+
+ # Compensate for yet another CVS bug.
+ $self -> _fixTag($tag);
+
+} # End of _setTag.
+
+# --------------------------------------------------------------------------
+# Validate an entry in one of the CVS files 'module' or 'val-tags'.
+# Warning: $tag must be in CVS format. Eg: release_1_23, not release_1.23.
+
+sub _validateObject
+{
+ my($self, $tag, $file, $mustBeAbsent) = @_;
+
+ $file = "$ENV{'CVSROOT'}/CVSROOT/$file";
+
+ open(INX, $file) || croak("Can't open($file): \nFailure: $!");
+
+ my($found) = 0;
+
+ while (<INX>)
+ {
+ $found = 1 if (/^$tag/);
+ }
+
+ close(INX);
+
+ croak("Failure: Tag not found: $tag in file $file")
+ if ( (! $found) && (! $mustBeAbsent) );
+
+ croak("Failure: Tag already present: $tag in file $file")
+ if ($found && $mustBeAbsent);
+
+} # End of _validateObject.
+
+# --------------------------------------------------------------------------
+
+# Autoload methods go after =cut, and are processed by the autosplit program.
+
+1;
+
+__END__
+
+=head1 NAME
+
+C<VCS::CVS> - Provide a simple interface to CVS (the Concurrent Versions System).
+
+You need to be clear in your mind about the 4 directories involved:
+
+=over 4
+
+=item *
+
+The directory where your source code resides before you import it into CVS.
+It is used only once - during the import phase. Call this $projectSource.
+
+=item *
+
+The directory into which you check out a read-write copy of the repository,
+in order to edit that copy. Call this $project. You will spend up to 100% of
+your time working within this directory structure.
+
+=item *
+
+The directory in which the repository resides. This is $CVSROOT. Thus
+$projectSource will be imported into $CVSROOT/$project.
+
+=item *
+
+The directory into which you get a read-only copy of the repository, in order to,
+say, make and ship that copy. Call this $someDir. It must not be $project.
+
+=back
+
+Note: You cannot have a directory called CVS in your home directory. That's
+just asking for trouble.
+
+=head1 SYNOPSIS
+
+ #!/usr/gnu/bin/perl -w
+
+ use integer;
+ use strict;
+
+ use VCS::CVS;
+
+ my($history) = 1;
+ my($initialMsg) = 'Initial version';
+ my($noChange) = 1;
+ my($nullTag) = '';
+ my($permissions) = 0775; # But not '0775'!
+ my($project) = 'project';
+ my($projectSource) = 'projectSource';
+ my($raw) = 0;
+ my($readOnly) = 0;
+ my($releaseTag) = 'release_0.00';
+ my($vendorTag) = 'vendorTag';
+ my($verbose) = 1;
+
+ # Note the anonymous hash in the next line, new as of V 1.10.
+
+ my($cvs) = VCS::CVS -> new({
+ 'project' => $project,
+ 'raw' => $raw,
+ 'verbose' => $verbose,
+ 'permissions' => $permissions,
+ 'history' => $history});
+
+ $cvs -> createRepository();
+ $cvs -> populate($projectSource, $vendorTag, $releaseTag, $initialMsg);
+ $cvs -> checkOut($readOnly, $nullTag, $project);
+
+ print join("\n", @{$cvs -> update($noChange)});
+ print "\n";
+ print join("\n", @{$cvs -> history()});
+
+ exit(0);
+
+=head1 DESCRIPTION
+
+The C<VCS::CVS> module provides an OO interface to CVS.
+
+VCS - Version Control System - is the prefix given to each Perl module which
+deals with some sort of source code control system.
+
+I have seen CVS corrupt binary files, even when run with CVS's binary option -kb.
+So, since CVS doesn't support binary files, neither does VCS::CVS.
+
+Stop press: CVS V 1.10 (with RCS 5.7) supports binary files.
+
+Subroutines whose names start with a '_' are not normally called by you.
+
+There is a test program included, but I have not yet worked out exactly how to
+set it up for make test. Stay tuned.
+
+=head1 INSTALLATION
+
+You install C<VCS::CVS>, as you would install any perl module library,
+by running these commands:
+
+ perl Makefile.PL
+ make
+ make test
+ make install
+
+If you want to install a private copy of C<VCS::CVS> in your home
+directory, then you should try to produce the initial Makefile with
+something like this command:
+
+ perl Makefile.PL LIB=~/perl
+ or
+ perl Makefile.PL LIB=C:/Perl/Site/Lib
+
+If, like me, you don't have permission to write man pages into unix system
+directories, use:
+
+ make pure_install
+
+instead of make install. This option is secreted in the middle of p 414 of the
+second edition of the dromedary book.
+
+=head1 WARNING re CVS bugs
+
+The following are my ideas as to what constitutes a bug in CVS:
+
+=over 4
+
+=item *
+
+The initial revision tag, supplied when populating the repository with
+'cvs import', is not saved into $CVSROOT/CVSROOT/val-tags.
+
+=item *
+
+The 'cvs tag' command does not always put the tag into 'val-tags'.
+
+=item *
+
+C<'cvs checkout -dNameOfDir'> fails if NameOfDir =~ /\/$/.
+
+=item *
+
+C<'cvs checkout -d NameOfDir'> inserts a leading space into the name of
+the directory it creates.
+
+=back
+
+=head1 WARNING re test environment
+
+This code has only been tested under Unix. Sorry.
+
+=head1 WARNING re project names 'v' directory names
+
+I assume your copy of the repository was checked out into a directory with
+the same name as the project, since I do a 'cd $HOME/$project' before running
+'cvs status', to see if your copy is up-to-date. This is because some activity is
+forbibben unless your copy is up-to-date. Typical cases of this include:
+
+=over 4
+
+=item *
+
+C<checkOut>
+
+=item *
+
+C<removeDirectory>
+
+=item *
+
+C<setTag>
+
+=back
+
+=head1 WARNING re shell intervention
+
+Some commands cause the shell to become involved, which, under Unix, will read your
+.cshrc or whatever, which in turn may set CVSROOT to something other than what you
+set it to before running your script. If this happens, panic...
+
+Actually, I think I've eliminated such cases. You hope so.
+
+=head1 WARNING re Perl bug
+
+As always, be aware that these 2 lines mean the same thing, sometimes:
+
+=over 4
+
+=item *
+
+$self -> {'thing'}
+
+=item *
+
+$self->{'thing'}
+
+=back
+
+The problem is the spaces around the ->. Inside double quotes, "...", the
+first space stops the dereference taking place. Outside double quotes the
+scanner correctly associates the $self token with the {'thing'} token.
+
+I regard this as a bug.
+
+=head1 addDirectory($dir, $subDir, $message)
+
+Add an existing directory to the project.
+
+$dir can be a full path, or relative to the CWD.
+
+=head1 addFile($dir, $file, $message)
+
+Add an existing file to the project.
+
+$dir can be a full path, or relative to the CWD.
+
+=head1 checkOut($readOnly, $tag, $dir)
+
+Prepare & perform 'cvs checkout'.
+
+You call checkOut, and it calls _checkOutDontCallMe.
+
+=over 4
+
+=item *
+
+$readOnly == 0 -> Check out files as read-write.
+
+=item *
+
+$readOnly == 1 -> Check out files as read-only.
+
+=back
+
+=over 4
+
+=item *
+
+$tag is Null -> Do not call upToDate; ie check out repository as is.
+
+=item *
+
+$tag is not Null -> Call upToDate; Croak if repository is not up-to-date.
+
+=back
+
+The value of $raw used in the call to new influences the handling of $tag:
+
+=over 4
+
+=item *
+
+$raw == 1 -> Your tag is passed as is to CVS.
+
+=item *
+
+$raw == 0 -> Your tag is assumed to be of the form release_1.23, and is
+converted to CVS's form release_1_23.
+
+=back
+
+$dir can be a full path, or relative to the CWD.
+
+=head1 commit($message)
+
+Commit changes.
+
+Called as appropriate by addFile, removeFile and removeDirectory,
+so you don't need to call it.
+
+=head1 createRepository()
+
+Create a repository, using the current $CVSROOT.
+
+This involves creating these files:
+
+=over 4
+
+=item *
+
+$ENV{'CVSROOT'}/CVSROOT/modules
+
+=item *
+
+$ENV{'CVSROOT'}/CVSROOT/val-tags
+
+=item *
+
+$ENV{'CVSROOT'}/CVSROOT/history
+
+=back
+
+Notes:
+
+=over 4
+
+=item *
+
+The 'modules' file contains these lines:
+
+ CVSROOT CVSROOT
+ modules CVSROOT modules
+ $self -> {'project'} $self -> {'project'}
+
+where $self -> {'project'} comes from the 'project' parameter to new()
+
+=item *
+
+The 'val-tags' file is initially empty
+
+=item *
+
+The 'history' file is only created if the 'history' parameter to new() is set.
+The file is initially empty
+
+=back
+
+=head1 getTags()
+
+Return a reference to a list of tags.
+
+See also: the $raw option to new().
+
+C<getTags> does not take a project name because tags belong to the repository
+as a whole, not to a project.
+
+=head1 history({})
+
+Report details from the history log, $CVSROOT/CVSROOT/history.
+
+You must have used new({'history' => 1}), or some other mechanism, to create
+the history file, before CVS starts logging changes into the history file.
+
+The anonymous hash takes any parameters 'cvs history' takes, and joins them
+with a single space. Eg:
+
+ $cvs -> history();
+
+ $cvs -> history({'-e' => ''});
+
+ $cvs -> history({'-xARM' => ''});
+
+ $cvs -> history({'-u' => $ENV{'LOGNAME'}, '-x' => 'A'});
+
+but not
+
+ $cvs -> history({'-xA' => 'M'});
+
+because it doesn't work.
+
+=head1 new({})
+
+Create a new object. See the synopsis.
+
+The anonymous hash takes these parameters, of which 'project' is the
+only required one.
+
+=over 4
+
+=item *
+
+'project' => 'killerApp'. The required name of the project. No default
+
+=back
+
+=over 4
+
+=item *
+
+'permissions' => 0775. Unix-specific stuff. Default. Do not use '0775'.
+
+=back
+
+=over 4
+
+=item *
+
+'history' => 0. Do not create $CVSROOT/CVSROOT/history when createRepository() is called. Default
+
+=item *
+
+'history' => 1. Create $CVSROOT/CVSROOT/history, which initiates 'cvs history' stuff
+
+=back
+
+=over 4
+
+=item *
+
+'raw' => 0. Convert tags from CVS format to real format. Eg: release_1.23. Default.
+
+=item *
+
+'raw' => 1. Return tags in raw CVS format. Eg: release_1_23.
+
+=back
+
+=over 4
+
+=item *
+
+'verbose' => 0. Do not report on the progress of mkpath/rmtree
+
+=item *
+
+'verbose' => 1. Report on the progress of mkpath/rmtree. Default
+
+=back
+
+=head1 populate($sourceDir, $vendorTag, $releaseTag, $message)
+
+Import an existing directory structure. But, (sub) import is a reserved word.
+
+Use this to populate a repository for the first time.
+
+The value used for $vendorTag is not important; CVS discards it.
+
+The value used to $releaseTag is important; CVS discards it (why?) but I
+force it to be the first tag in $CVSROOT/CVSROOT/val-tags. Thus you
+should supply a meaningful value. Thus 'release_0_00' is strongly, repeat
+strongly, recommended.
+
+The value of $raw used in the call to new influences the handling of $tag:
+
+=over 4
+
+=item *
+
+$raw == 1 -> Your tag is passed as is to CVS.
+
+=item *
+
+$raw == 0 -> Your tag is assumed to be of the form release_1.23, and is
+converted to CVS's form release_1_23.
+
+=back
+
+=head1 removeDirectory($dir)
+
+Remove a directory from the project.
+
+This deletes the directory (and all its files) from your working copy
+of the repository, as well as deleting them from the repository.
+
+Warning: $dir will have $CVSROOT and $HOME prepended by this code.
+Ie: $dir starts from - but excludes - your home directory
+(assuming, of course, you've checked out into your home directory...).
+
+You can't remove the current directory, or a parent.
+
+=head1 removeFile($dir, $file, $message)
+
+Remove a file from the project.
+
+This deletes the file from your working copy of the repository,
+as well as deleting it from the repository.
+
+$dir can be a full path, or relative to the CWD.
+$file is relative to $dir.
+
+=head1 runOrCroak()
+
+The standard way to run a system command and report on the result.
+
+=head1 setTag($tag)
+
+Tag the repository.
+
+You call setTag, and it calls _setTag.
+
+The value of $raw used in the call to new influences the handling of $tag:
+
+=over 4
+
+=item *
+
+$raw == 1 -> Your tag is passed as is to CVS.
+
+=item *
+
+$raw == 0 -> Your tag is assumed to be of the form release_1.23, and is
+converted to CVS's form release_1_23.
+
+=back
+
+=head1 stripCVSDirs($dir)
+
+Delete all CVS directories and files from a copy of the repository.
+
+Each user directory contains a CVS sub-directory, which holds 3 files:
+
+=over 4
+
+=item *
+
+Entries
+
+=item *
+
+Repository
+
+=item *
+
+Root
+
+=back
+
+Zap 'em.
+
+=head1 status()
+
+Run cvs status.
+
+Return a reference to a list of lines.
+
+Only called by upToDate(), but you may call it.
+
+=head1 update($noChange)
+
+Run 'cvs C<-q> [C<-n>] update', returning a reference to a list of lines.
+Each line will start with one of [UARMC?], as per the CVS docs.
+
+$cvs -> update(1) is a good way to get a list of uncommited changes, etc.
+
+=over 4
+
+=item *
+
+$noChange == 0 -> Do not add C<-n> to the cvs command. Ie update your working copy
+
+=item *
+
+$noChange == 1 -> Add C<-n> to the cvs command. Do not change any files
+
+=back
+
+=head1 upToDate()
+
+=over 4
+
+=item *
+
+return == 0 -> Repository not up-to-date.
+
+=item *
+
+return == 1 -> Up-to-date.
+
+=back
+
+=head1 _checkOutDontCallMe($readOnly, $tag, $dir)
+
+Checkout a current copy of the project.
+
+You call checkOut, and it calls this.
+
+=over 4
+
+=item *
+
+$readOnly == 0 -> Check out files as read-write.
+
+=item *
+
+$readOnly == 1 -> Check out files as read-only.
+
+=back
+
+=head1 _fixTag($tag)
+
+Fix a tag which CVS failed to add.
+
+Warning: $tag must be in CVS format: release_1_23, not release_1.23.
+
+=head1 _mkpathOrCroak($self, $dir)
+
+There is no need for you to call this.
+
+=head1 _readFile($file)
+
+Return a reference to a list of lines.
+
+There is no need for you to call this.
+
+=head1 _setTag($tag)
+
+Tag the current version of the project.
+
+Warning: $tag must be in CVS format: release_1_23, not release_1.23.
+
+You call setTag and it calls this.
+
+=head1 _validateObject($tag, $file, $mustBeAbsent)
+
+Validate an entry in one of the CVS files 'module' or 'val-tags'.
+
+Warning: $tag must be in CVS format: release_1_23, not release_1.23.
+
+=head1 AUTHOR
+
+C<VCS::CVS> was written by Ron Savage I<E<lt>rpsavage@ozemail.com.auE<gt>> in 1998.
+
+=head1 LICENCE
+
+This program is free software; you can redistribute it and/or modify it under
+the same terms as Perl itself.
diff --git a/cpan/dist/VCS-CVS/Changes.txt b/cpan/dist/VCS-CVS/Changes.txt
new file mode 100644
index 00000000..1ac65c5c
--- /dev/null
+++ b/cpan/dist/VCS-CVS/Changes.txt
@@ -0,0 +1,29 @@
+Revision history for Perl extension VCS::CVS.
+
+2.00 17-Jun-99
+--------------
+o Change parameters to new(). It now accepts an anonymous hash
+o Add history parameter to new()
+o Add history(). It accepts an anonymous hash of 'cvs history' parameters,
+ and returns a ref to a list
+o Add update(). It accepts an optional boolean to active the -n in
+ 'cvs update -n', and returns a ref to a list
+o Fix 2 bugs whereby chdir() was called at the wrong time
+
+1.04 26-May-99
+--------------
+o Ensure POD survives buggy pod2man
+o Ship Readme.txt, the output of pod2text
+
+1.03 19-Apr-99
+--------------
+o Patch Makefile.PL to support ActivePerl's ppm.
+
+1.02 30-Mar-99
+--------------
+o Original version; created by h2xs 1.18
+
+Use h2xs to create the skeleton for CVS.pm.
+
+Fix populate & _setTag so they do a chdir
+back before calling _fixTag.
diff --git a/cpan/dist/VCS-CVS/MANIFEST b/cpan/dist/VCS-CVS/MANIFEST
new file mode 100644
index 00000000..1aeb7df0
--- /dev/null
+++ b/cpan/dist/VCS-CVS/MANIFEST
@@ -0,0 +1,8 @@
+Changes.txt
+CVS.pm
+MANIFEST
+MANIFEST.SKIP
+Makefile.PL
+Readme.txt
+test.pl
+t/base.t
diff --git a/cpan/dist/VCS-CVS/MANIFEST.SKIP b/cpan/dist/VCS-CVS/MANIFEST.SKIP
new file mode 100644
index 00000000..06c7fa7b
--- /dev/null
+++ b/cpan/dist/VCS-CVS/MANIFEST.SKIP
@@ -0,0 +1,3 @@
+Makefile$
+^blib
+^pm_to_blib
diff --git a/cpan/dist/VCS-CVS/Makefile.PL b/cpan/dist/VCS-CVS/Makefile.PL
new file mode 100644
index 00000000..87b9d0b2
--- /dev/null
+++ b/cpan/dist/VCS-CVS/Makefile.PL
@@ -0,0 +1,29 @@
+use ExtUtils::MakeMaker;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+
+WriteMakefile
+(
+ ($] ge '5.005') ?
+ (
+ 'AUTHOR' => 'Ron Savage (rpsavage@ozemail.com.au)',
+ 'ABSTRACT' => "Interface to GNU's CVS",
+ ) : (),
+'clean' =>
+ {
+ 'FILES' => 'blib/* Makefile VCS-CVS-*'
+ },
+'dist' =>
+ {
+ 'COMPRESS' => 'gzip',
+ 'SUFFIX' => 'gz'
+ },
+'DISTNAME' => 'VCS-CVS',
+'NAME' => 'VCS::CVS',
+'PM' =>
+ {
+ 'CVS.pm' => '$(INST_LIBDIR)/CVS.pm',
+ },
+'PREREQ_PM' => {},
+'VERSION_FROM' => 'CVS.pm'
+);
diff --git a/cpan/dist/VCS-CVS/Readme.txt b/cpan/dist/VCS-CVS/Readme.txt
new file mode 100644
index 00000000..6ac40487
--- /dev/null
+++ b/cpan/dist/VCS-CVS/Readme.txt
@@ -0,0 +1,739 @@
+.rn '' }`
+''' $RCSfile$$Revision$$Date$
+'''
+''' $Log$
+'''
+.de Sh
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp
+.if t .sp .5v
+.if n .sp
+..
+.de Ip
+.br
+.ie \\n(.$>=3 .ne \\$3
+.el .ne 3
+.IP "\\$1" \\$2
+..
+.de Vb
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve
+.ft R
+
+.fi
+..
+'''
+'''
+''' Set up \*(-- to give an unbreakable dash;
+''' string Tr holds user defined translation string.
+''' Bell System Logo is used as a dummy character.
+'''
+.tr \(*W-|\(bv\*(Tr
+.ie n \{\
+.ds -- \(*W-
+.ds PI pi
+.if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+.if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+.ds L" ""
+.ds R" ""
+''' \*(M", \*(S", \*(N" and \*(T" are the equivalent of
+''' \*(L" and \*(R", except that they are used on ".xx" lines,
+''' such as .IP and .SH, which do another additional levels of
+''' double-quote interpretation
+.ds M" """
+.ds S" """
+.ds N" """""
+.ds T" """""
+.ds L' '
+.ds R' '
+.ds M' '
+.ds S' '
+.ds N' '
+.ds T' '
+'br\}
+.el\{\
+.ds -- \(em\|
+.tr \*(Tr
+.ds L" ``
+.ds R" ''
+.ds M" ``
+.ds S" ''
+.ds N" ``
+.ds T" ''
+.ds L' `
+.ds R' '
+.ds M' `
+.ds S' '
+.ds N' `
+.ds T' '
+.ds PI \(*p
+'br\}
+.\" If the F register is turned on, we'll generate
+.\" index entries out stderr for the following things:
+.\" TH Title
+.\" SH Header
+.\" Sh Subsection
+.\" Ip Item
+.\" X<> Xref (embedded
+.\" Of course, you have to process the output yourself
+.\" in some meaninful fashion.
+.if \nF \{
+.de IX
+.tm Index:\\$1\t\\n%\t"\\$2"
+..
+.nr % 0
+.rr F
+.\}
+.TH CVS 3 "perl 5.005, patch 02" "17/Jun/99" "User Contributed Perl Documentation"
+.UC
+.if n .hy 0
+.if n .na
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.de CQ \" put $1 in typewriter font
+.ft CW
+'if n "\c
+'if t \\&\\$1\c
+'if n \\&\\$1\c
+'if n \&"
+\\&\\$2 \\$3 \\$4 \\$5 \\$6 \\$7
+'.ft R
+..
+.\" @(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2
+. \" AM - accent mark definitions
+.bd B 3
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds ? ?
+. ds ! !
+. ds /
+. ds q
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds ? \s-2c\h'-\w'c'u*7/10'\u\h'\*(#H'\zi\d\s+2\h'\w'c'u*8/10'
+. ds ! \s-2\(or\s+2\h'-\w'\(or'u'\v'-.8m'.\v'.8m'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+. ds q o\h'-\w'o'u*8/10'\s-4\v'.4m'\z\(*i\v'-.4m'\s+4\h'\w'o'u*8/10'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds v \\k:\h'-(\\n(.wu*9/10-\*(#H)'\v'-\*(#V'\*(#[\s-4v\s0\v'\*(#V'\h'|\\n:u'\*(#]
+.ds _ \\k:\h'-(\\n(.wu*9/10-\*(#H+(\*(#F*2/3))'\v'-.4m'\z\(hy\v'.4m'\h'|\\n:u'
+.ds . \\k:\h'-(\\n(.wu*8/10)'\v'\*(#V*4/10'\z.\v'-\*(#V*4/10'\h'|\\n:u'
+.ds 3 \*(#[\v'.2m'\s-2\&3\s0\v'-.2m'\*(#]
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+.ds oe o\h'-(\w'o'u*4/10)'e
+.ds Oe O\h'-(\w'O'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds v \h'-1'\o'\(aa\(ga'
+. ds _ \h'-1'^
+. ds . \h'-1'.
+. ds 3 3
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+. ds oe oe
+. ds Oe OE
+.\}
+.rm #[ #] #H #V #F C
+.SH "NAME"
+\f(CWVCS::CVS\fR \- Provide a simple interface to CVS (the Concurrent Versions System).
+.PP
+You need to be clear in your mind about the 4 directories involved:
+.Ip "\(bu" 4
+The directory where your source code resides before you import it into \s-1CVS\s0.
+It is used only once \- during the import phase. Call this \f(CW$projectSource\fR.
+.Ip "\(bu" 4
+The directory into which you check out a read-write copy of the repository,
+in order to edit that copy. Call this \f(CW$project\fR. You will spend up to 100% of
+your time working within this directory structure.
+.Ip "\(bu" 4
+The directory in which the repository resides. This is \f(CW$CVSROOT\fR. Thus
+\f(CW$projectSource\fR will be imported into \f(CW$CVSROOT\fR/$project.
+.Ip "\(bu" 4
+The directory into which you get a read-only copy of the repository, in order to,
+say, make and ship that copy. Call this \f(CW$someDir\fR. It must not be \f(CW$project\fR.
+.PP
+Note: You cannot have a directory called \s-1CVS\s0 in your home directory. That's
+just asking for trouble.
+.SH "SYNOPSIS"
+.PP
+.Vb 1
+\& #!/usr/gnu/bin/perl -w
+.Ve
+.Vb 2
+\& use integer;
+\& use strict;
+.Ve
+.Vb 1
+\& use VCS::CVS;
+.Ve
+.Vb 12
+\& my($history) = 1;
+\& my($initialMsg) = 'Initial version';
+\& my($noChange) = 1;
+\& my($nullTag) = '';
+\& my($permissions) = 0775; # But not '0775'!
+\& my($project) = 'project';
+\& my($projectSource) = 'projectSource';
+\& my($raw) = 0;
+\& my($readOnly) = 0;
+\& my($releaseTag) = 'release_0.00';
+\& my($vendorTag) = 'vendorTag';
+\& my($verbose) = 1;
+.Ve
+.Vb 1
+\& # Note the anonymous hash in the next line, new as of V 1.10.
+.Ve
+.Vb 6
+\& my($cvs) = VCS::CVS -> new({
+\& 'project' => $project,
+\& 'raw' => $raw,
+\& 'verbose' => $verbose,
+\& 'permissions' => $permissions,
+\& 'history' => $history});
+.Ve
+.Vb 3
+\& $cvs -> createRepository();
+\& $cvs -> populate($projectSource, $vendorTag, $releaseTag, $initialMsg);
+\& $cvs -> checkOut($readOnly, $nullTag, $project);
+.Ve
+.Vb 3
+\& print join("\en", @{$cvs -> update($noChange)});
+\& print "\en";
+\& print join("\en", @{$cvs -> history()});
+.Ve
+.Vb 1
+\& exit(0);
+.Ve
+.SH "DESCRIPTION"
+The \f(CWVCS::CVS\fR module provides an OO interface to CVS.
+.PP
+VCS \- Version Control System \- is the prefix given to each Perl module which
+deals with some sort of source code control system.
+.PP
+I have seen CVS corrupt binary files, even when run with CVS's binary option \-kb.
+So, since CVS doesn't support binary files, neither does VCS::CVS.
+.PP
+Stop press: CVS V 1.10 (with RCS 5.7) supports binary files.
+.PP
+Subroutines whose names start with a \*(L'_\*(R' are not normally called by you.
+.PP
+There is a test program included, but I have not yet worked out exactly how to
+set it up for make test. Stay tuned.
+.SH "INSTALLATION"
+You install \f(CWVCS::CVS\fR, as you would install any perl module library,
+by running these commands:
+.PP
+.Vb 4
+\& perl Makefile.PL
+\& make
+\& make test
+\& make install
+.Ve
+If you want to install a private copy of \f(CWVCS::CVS\fR in your home
+directory, then you should try to produce the initial Makefile with
+something like this command:
+.PP
+.Vb 3
+\& perl Makefile.PL LIB=~/perl
+\& or
+\& perl Makefile.PL LIB=C:/Perl/Site/Lib
+.Ve
+If, like me, you don't have permission to write man pages into unix system
+directories, use:
+.PP
+.Vb 1
+\& make pure_install
+.Ve
+instead of make install. This option is secreted in the middle of p 414 of the
+second edition of the dromedary book.
+.SH "WARNING re CVS bugs"
+The following are my ideas as to what constitutes a bug in CVS:
+.Ip "\(bu" 4
+The initial revision tag, supplied when populating the repository with
+\&'cvs import\*(R', is not saved into \f(CW$CVSROOT\fR/\s-1CVSROOT/\s0val-tags.
+.Ip "\(bu" 4
+The \*(L'cvs tag\*(R' command does not always put the tag into \*(L'val-tags\*(R'.
+.Ip "\(bu" 4
+\&\f(CW'cvs checkout -dNameOfDir'\fR fails if NameOfDir =~ /\e/$/.
+.Ip "\(bu" 4
+\&\f(CW'cvs checkout -d NameOfDir'\fR inserts a leading space into the name of
+the directory it creates.
+.SH "WARNING re test environment"
+This code has only been tested under Unix. Sorry.
+.SH "WARNING re project names \*(M'v\*(S' directory names"
+I assume your copy of the repository was checked out into a directory with
+the same name as the project, since I do a \*(L'cd \f(CW$HOME\fR/$project\*(R' before running
+\&'cvs status\*(R', to see if your copy is up-to-date. This is because some activity is
+forbibben unless your copy is up-to-date. Typical cases of this include:
+.Ip "\(bu" 4
+\f(CWcheckOut\fR
+.Ip "\(bu" 4
+\f(CWremoveDirectory\fR
+.Ip "\(bu" 4
+\f(CWsetTag\fR
+.SH "WARNING re shell intervention"
+Some commands cause the shell to become involved, which, under Unix, will read your
+\&.cshrc or whatever, which in turn may set CVSROOT to something other than what you
+set it to before running your script. If this happens, panic...
+.PP
+Actually, I think I've eliminated such cases. You hope so.
+.SH "WARNING re Perl bug"
+As always, be aware that these 2 lines mean the same thing, sometimes:
+.Ip "\(bu" 4
+$self \-> {'thing'}
+.Ip "\(bu" 4
+$self->{'thing'}
+.PP
+The problem is the spaces around the \->. Inside double quotes, \*(L"...\*(R", the
+first space stops the dereference taking place. Outside double quotes the
+scanner correctly associates the \f(CW$self\fR token with the {'thing'} token.
+.PP
+I regard this as a bug.
+.SH "\fIaddDirectory\fR\|($dir, \f(CW$subDir\fR, \f(CW$message\fR)"
+Add an existing directory to the project.
+.PP
+$dir can be a full path, or relative to the CWD.
+.SH "\fIaddFile\fR\|($dir, \f(CW$file\fR, \f(CW$message\fR)"
+Add an existing file to the project.
+.PP
+$dir can be a full path, or relative to the CWD.
+.SH "\fIcheckOut\fR\|($readOnly, \f(CW$tag\fR, \f(CW$dir\fR)"
+Prepare & perform \*(L'cvs checkout\*(R'.
+.PP
+You call checkOut, and it calls _checkOutDontCallMe.
+.Ip "\(bu" 4
+$readOnly == 0 \-> Check out files as read-write.
+.Ip "\(bu" 4
+$readOnly == 1 \-> Check out files as read-only.
+.Ip "\(bu" 4
+$tag is Null \-> Do not call upToDate; ie check out repository as is.
+.Ip "\(bu" 4
+$tag is not Null \-> Call upToDate; Croak if repository is not up-to-date.
+.PP
+The value of \f(CW$raw\fR used in the call to new influences the handling of \f(CW$tag:\fR
+.Ip "\(bu" 4
+$raw == 1 \-> Your tag is passed as is to \s-1CVS\s0.
+.Ip "\(bu" 4
+$raw == 0 \-> Your tag is assumed to be of the form release_1.23, and is
+converted to \s-1CVS\s0's form release_1_23.
+.PP
+$dir can be a full path, or relative to the \s-1CWD\s0.
+.SH "\fIcommit\fR\|($message)"
+Commit changes.
+.PP
+Called as appropriate by addFile, removeFile and removeDirectory,
+so you don't need to call it.
+.SH "\fIcreateRepository()\fR"
+Create a repository, using the current \f(CW$CVSROOT\fR.
+.PP
+This involves creating these files:
+.Ip "\(bu" 4
+$\s-1ENV\s0{'\s-1CVSROOT\s0'}/\s-1CVSROOT/\s0modules
+.Ip "\(bu" 4
+$\s-1ENV\s0{'\s-1CVSROOT\s0'}/\s-1CVSROOT/\s0val-tags
+.Ip "\(bu" 4
+$\s-1ENV\s0{'\s-1CVSROOT\s0'}/\s-1CVSROOT/\s0history
+.PP
+Notes:
+.Ip "\(bu" 4
+The \*(L'modules\*(R' file contains these lines:
+.Sp
+.Vb 3
+\& CVSROOT CVSROOT
+\& modules CVSROOT modules
+\& $self -> {'project'} $self -> {'project'}
+.Ve
+where \f(CW$self\fR \-> {'project'} comes from the \*(L'project\*(R' parameter to \fInew()\fR
+.Ip "\(bu" 4
+The \*(L'val-tags\*(R' file is initially empty
+.Ip "\(bu" 4
+The \*(L'history\*(R' file is only created if the \*(L'history\*(R' parameter to \fInew()\fR is set.
+The file is initially empty
+.SH "\fIgetTags()\fR"
+Return a reference to a list of tags.
+.PP
+See also: the \f(CW$raw\fR option to \fInew()\fR.
+.PP
+\f(CWgetTags\fR does not take a project name because tags belong to the repository
+as a whole, not to a project.
+.SH "\fIhistory\fR\|({})"
+Report details from the history log, \f(CW$CVSROOT\fR/CVSROOT/history.
+.PP
+You must have used \fInew\fR\|({'history\*(R' => 1}), or some other mechanism, to create
+the history file, before CVS starts logging changes into the history file.
+.PP
+The anonymous hash takes any parameters \*(L'cvs history\*(R' takes, and joins them
+with a single space. Eg:
+.PP
+.Vb 1
+\& $cvs -> history();
+.Ve
+.Vb 1
+\& $cvs -> history({'-e' => ''});
+.Ve
+.Vb 1
+\& $cvs -> history({'-xARM' => ''});
+.Ve
+.Vb 1
+\& $cvs -> history({'-u' => $ENV{'LOGNAME'}, '-x' => 'A'});
+.Ve
+but not
+.PP
+.Vb 1
+\& $cvs -> history({'-xA' => 'M'});
+.Ve
+because it doesn't work.
+.SH "\fInew\fR\|({})"
+Create a new object. See the synopsis.
+.PP
+The anonymous hash takes these parameters, of which \*(L'project\*(R' is the
+only required one.
+.Ip "\(bu" 4
+\&'project\*(R' => \*(L'killerApp\*(R'. The required name of the project. No default
+.Ip "\(bu" 4
+\&'permissions\*(R' => 0775. Unix-specific stuff. Default. Do not use \*(L'0775\*(R'.
+.Ip "\(bu" 4
+\&'history\*(R' => 0. Do not create \f(CW$CVSROOT\fR/\s-1CVSROOT/\s0history when \fIcreateRepository()\fR is called. Default
+.Ip "\(bu" 4
+\&'history\*(R' => 1. Create \f(CW$CVSROOT\fR/\s-1CVSROOT/\s0history, which initiates \*(L'cvs history\*(R' stuff
+.Ip "\(bu" 4
+\&'raw\*(R' => 0. Convert tags from \s-1CVS\s0 format to real format. Eg: release_1.23. Default.
+.Ip "\(bu" 4
+\&'raw\*(R' => 1. Return tags in raw \s-1CVS\s0 format. Eg: release_1_23.
+.Ip "\(bu" 4
+\&'verbose\*(R' => 0. Do not report on the progress of mkpath/rmtree
+.Ip "\(bu" 4
+\&'verbose\*(R' => 1. Report on the progress of mkpath/rmtree. Default
+.SH "\fIpopulate\fR\|($sourceDir, \f(CW$vendorTag\fR, \f(CW$releaseTag\fR, \f(CW$message\fR)"
+Import an existing directory structure. But, (sub) import is a reserved word.
+.PP
+Use this to populate a repository for the first time.
+.PP
+The value used for \f(CW$vendorTag\fR is not important; CVS discards it.
+.PP
+The value used to \f(CW$releaseTag\fR is important; CVS discards it (why?) but I
+force it to be the first tag in \f(CW$CVSROOT\fR/CVSROOT/val-tags. Thus you
+should supply a meaningful value. Thus \*(L'release_0_00\*(R' is strongly, repeat
+strongly, recommended.
+.PP
+The value of \f(CW$raw\fR used in the call to new influences the handling of \f(CW$tag:\fR
+.Ip "\(bu" 4
+$raw == 1 \-> Your tag is passed as is to \s-1CVS\s0.
+.Ip "\(bu" 4
+$raw == 0 \-> Your tag is assumed to be of the form release_1.23, and is
+converted to \s-1CVS\s0's form release_1_23.
+.SH "\fIremoveDirectory\fR\|($dir)"
+Remove a directory from the project.
+.PP
+This deletes the directory (and all its files) from your working copy
+of the repository, as well as deleting them from the repository.
+.PP
+Warning: \f(CW$dir\fR will have \f(CW$CVSROOT\fR and \f(CW$HOME\fR prepended by this code.
+Ie: \f(CW$dir\fR starts from \- but excludes \- your home directory
+(assuming, of course, you've checked out into your home directory...).
+.PP
+You can't remove the current directory, or a parent.
+.SH "\fIremoveFile\fR\|($dir, \f(CW$file\fR, \f(CW$message\fR)"
+Remove a file from the project.
+.PP
+This deletes the file from your working copy of the repository,
+as well as deleting it from the repository.
+.PP
+$dir can be a full path, or relative to the CWD.
+\f(CW$file\fR is relative to \f(CW$dir\fR.
+.SH "\fIrunOrCroak()\fR"
+The standard way to run a system command and report on the result.
+.SH "\fIsetTag\fR\|($tag)"
+Tag the repository.
+.PP
+You call setTag, and it calls _setTag.
+.PP
+The value of \f(CW$raw\fR used in the call to new influences the handling of \f(CW$tag:\fR
+.Ip "\(bu" 4
+$raw == 1 \-> Your tag is passed as is to \s-1CVS\s0.
+.Ip "\(bu" 4
+$raw == 0 \-> Your tag is assumed to be of the form release_1.23, and is
+converted to \s-1CVS\s0's form release_1_23.
+.SH "\fIstripCVSDirs\fR\|($dir)"
+Delete all CVS directories and files from a copy of the repository.
+.PP
+Each user directory contains a CVS sub-directory, which holds 3 files:
+.Ip "\(bu" 4
+Entries
+.Ip "\(bu" 4
+Repository
+.Ip "\(bu" 4
+Root
+.PP
+Zap \*(L'em.
+.SH "\fIstatus()\fR"
+Run cvs status.
+.PP
+Return a reference to a list of lines.
+.PP
+Only called by \fIupToDate()\fR, but you may call it.
+.SH "\fIupdate\fR\|($noChange)"
+Run \*(L'cvs \f(CW-q\fR [\f(CW-n\fR] update\*(R', returning a reference to a list of lines.
+Each line will start with one of [UARMC?], as per the CVS docs.
+.PP
+$cvs \-> \fIupdate\fR\|(1) is a good way to get a list of uncommited changes, etc.
+.Ip "\(bu" 4
+$noChange == 0 \-> Do not add \f(CW-n\fR to the cvs command. Ie update your working copy
+.Ip "\(bu" 4
+$noChange == 1 \-> Add \f(CW-n\fR to the cvs command. Do not change any files
+.SH "\fIupToDate()\fR"
+.Ip "\(bu" 4
+return == 0 \-> Repository not up-to-date.
+.Ip "\(bu" 4
+return == 1 \-> Up-to-date.
+.SH "\fI_checkOutDontCallMe\fR\|($readOnly, \f(CW$tag\fR, \f(CW$dir\fR)"
+Checkout a current copy of the project.
+.PP
+You call checkOut, and it calls this.
+.Ip "\(bu" 4
+$readOnly == 0 \-> Check out files as read-write.
+.Ip "\(bu" 4
+$readOnly == 1 \-> Check out files as read-only.
+.SH "\fI_fixTag\fR\|($tag)"
+Fix a tag which CVS failed to add.
+.PP
+Warning: \f(CW$tag\fR must be in CVS format: release_1_23, not release_1.23.
+.SH "\fI_mkpathOrCroak\fR\|($self, \f(CW$dir\fR)"
+There is no need for you to call this.
+.SH "\fI_readFile\fR\|($file)"
+Return a reference to a list of lines.
+.PP
+There is no need for you to call this.
+.SH "\fI_setTag\fR\|($tag)"
+Tag the current version of the project.
+.PP
+Warning: \f(CW$tag\fR must be in CVS format: release_1_23, not release_1.23.
+.PP
+You call setTag and it calls this.
+.SH "\fI_validateObject\fR\|($tag, \f(CW$file\fR, \f(CW$mustBeAbsent\fR)"
+Validate an entry in one of the CVS files \*(L'module\*(R' or \*(L'val-tags\*(R'.
+.PP
+Warning: \f(CW$tag\fR must be in CVS format: release_1_23, not release_1.23.
+.SH "AUTHOR"
+\f(CWVCS::CVS\fR was written by Ron Savage \fI<rpsavage@ozemail.com.au>\fR in 1998.
+.SH "LICENCE"
+This program is free software; you can redistribute it and/or modify it under
+the same terms as Perl itself.
+
+.rn }` ''
+.IX Title "CVS 3"
+.IX Name "C<VCS::CVS> - Provide a simple interface to CVS (the Concurrent Versions System)."
+
+.IX Header "NAME"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Header "SYNOPSIS"
+
+.IX Header "DESCRIPTION"
+
+.IX Header "INSTALLATION"
+
+.IX Header "WARNING re CVS bugs"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Header "WARNING re test environment"
+
+.IX Header "WARNING re project names \*(M'v\*(S' directory names"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Header "WARNING re shell intervention"
+
+.IX Header "WARNING re Perl bug"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Header "\fIaddDirectory\fR\|($dir, \f(CW$subDir\fR, \f(CW$message\fR)"
+
+.IX Header "\fIaddFile\fR\|($dir, \f(CW$file\fR, \f(CW$message\fR)"
+
+.IX Header "\fIcheckOut\fR\|($readOnly, \f(CW$tag\fR, \f(CW$dir\fR)"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Header "\fIcommit\fR\|($message)"
+
+.IX Header "\fIcreateRepository()\fR"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Header "\fIgetTags()\fR"
+
+.IX Header "\fIhistory\fR\|({})"
+
+.IX Header "\fInew\fR\|({})"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Header "\fIpopulate\fR\|($sourceDir, \f(CW$vendorTag\fR, \f(CW$releaseTag\fR, \f(CW$message\fR)"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Header "\fIremoveDirectory\fR\|($dir)"
+
+.IX Header "\fIremoveFile\fR\|($dir, \f(CW$file\fR, \f(CW$message\fR)"
+
+.IX Header "\fIrunOrCroak()\fR"
+
+.IX Header "\fIsetTag\fR\|($tag)"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Header "\fIstripCVSDirs\fR\|($dir)"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Header "\fIstatus()\fR"
+
+.IX Header "\fIupdate\fR\|($noChange)"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Header "\fIupToDate()\fR"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Header "\fI_checkOutDontCallMe\fR\|($readOnly, \f(CW$tag\fR, \f(CW$dir\fR)"
+
+.IX Item "\(bu"
+
+.IX Item "\(bu"
+
+.IX Header "\fI_fixTag\fR\|($tag)"
+
+.IX Header "\fI_mkpathOrCroak\fR\|($self, \f(CW$dir\fR)"
+
+.IX Header "\fI_readFile\fR\|($file)"
+
+.IX Header "\fI_setTag\fR\|($tag)"
+
+.IX Header "\fI_validateObject\fR\|($tag, \f(CW$file\fR, \f(CW$mustBeAbsent\fR)"
+
+.IX Header "AUTHOR"
+
+.IX Header "LICENCE"
+
diff --git a/cpan/dist/VCS-CVS/t/base.t b/cpan/dist/VCS-CVS/t/base.t
new file mode 100644
index 00000000..b690dc50
--- /dev/null
+++ b/cpan/dist/VCS-CVS/t/base.t
@@ -0,0 +1,38 @@
+# -*- perl -*-
+
+use integer;
+use strict;
+
+use vars qw($loaded);
+
+BEGIN
+{
+ $| = 1;
+ print "1..2\n";
+}
+
+END
+{
+ print "not ok 1\n" if (! $loaded);
+}
+
+use VCS::CVS;
+
+$loaded = 1;
+
+print "ok 1\n";
+
+my($testNum) = 1;
+
+sub Test($)
+{
+ my($result) = shift;
+ $testNum++;
+ print ( ($result ? "" : "not "), "ok $testNum\n");
+ $result;
+}
+
+$ENV{'CVSROOT'} = '.';
+my($spell) = VCS::CVS -> new({'project' => 'test'});
+
+Test($spell); # or print "Error...\n";
diff --git a/cpan/dist/VCS-CVS/test.pl b/cpan/dist/VCS-CVS/test.pl
new file mode 100755
index 00000000..f88e4bf8
--- /dev/null
+++ b/cpan/dist/VCS-CVS/test.pl
@@ -0,0 +1,276 @@
+#!/usr/gnu/bin/perl -w
+#
+# Name:
+# test.pl.
+#
+# Purpose:
+# To test $PERL5LIB/VCS/CVS.pm.
+#
+# Warning:
+# setenv CVSROOT <somethingHarmless> during this.
+
+use integer;
+use strict;
+
+use Cwd;
+use File::Basename;
+use File::Copy;
+use File::Path;
+use VCS::CVS;
+
+#------------------------------------------------------------------
+
+sub addDirectory
+{
+ my($cvs, $projectName, $subDirName, $fileName, $addDirMsg,
+ $addFileMsg, $verbose, $permissions) = @_;
+
+ &init("$projectName/$subDirName", $fileName, $verbose, $permissions);
+
+ &heading('addDirectory');
+ $cvs -> addDirectory($projectName, $subDirName, $addDirMsg);
+
+ print "\n";
+
+ # We can only add a file if we haven't used a sticky tag.
+ if ($projectName !~ /Strip/)
+ {
+ $fileName = fileparse($fileName, '');
+
+ &heading('addFile');
+ $cvs -> addFile("$projectName/$subDirName", $fileName, $addFileMsg);
+
+ print "\n";
+ }
+
+} # End of addDirectory.
+
+#------------------------------------------------------------------
+
+sub checkOut
+{
+ my($cvs, $readOnly, $dirName, $oldTag) = @_;
+
+ &heading('checkOut');
+ $cvs -> checkOut($readOnly, $oldTag, $dirName);
+
+ &printDir($dirName);
+
+ print "\n";
+
+} # End of checkOut.
+
+#------------------------------------------------------------------
+
+sub createRepository
+{
+ my($cvs, $projectSource, $vendorTag, $releaseTag, $initialMsg) = @_;
+
+ &heading('createRepository');
+ $cvs -> createRepository();
+
+ print "\n";
+
+ &heading('populate');
+ $cvs -> populate($projectSource, $vendorTag, $releaseTag, $initialMsg);
+
+ print "\n";
+
+} # End of creatRepository.
+
+#------------------------------------------------------------------
+
+sub getTags
+{
+ my($cvs) = @_;
+
+ &heading('getTags');
+ my($tagRef) = $cvs -> getTags();
+
+ print "Tags: \n";
+
+ for (sort(@$tagRef) )
+ {
+ print "$_\n";
+ }
+
+} # End of getTags.
+
+#------------------------------------------------------------------
+
+sub heading
+{
+ my($heading) = @_;
+
+ print "$heading\n";
+ print '-' x (length($heading) ), "\n";
+
+} # End of heading.
+
+#------------------------------------------------------------------
+
+sub init
+{
+ my($projectSource, $fileName, $verbose, $permissions) = @_;
+
+ my($destination) = "$ENV{'HOME'}/$projectSource";
+
+ &heading("rmtree+mkpath($destination)");
+ rmtree($destination, $verbose);
+ mkpath($destination, $verbose, $permissions);
+
+ copy($fileName, $destination);
+
+ &printDir($destination);
+
+ print "\n";
+
+} # End of init.
+
+#------------------------------------------------------------------
+
+sub printDir
+{
+ my($dirName) = @_;
+
+ opendir(INX, $dirName) || die("Can't opendir($dirName): $!");
+ my(@file) = readdir(INX);
+ closedir(INX);
+
+ print "Directory: $dirName. Files: \n";
+
+ for (@file)
+ {
+ print "$_\n";
+ }
+
+} # End of printDir.
+
+#------------------------------------------------------------------
+
+sub setTag
+{
+ my($cvs, $newTag) = @_;
+
+ # my($cvs, $dirName, $fileName, $newTag) = @_;
+ #
+ # Edit file, to cause failure of upToDate call within setTag.
+ # chdir($dirName) || die(Can't chdir($dirName): $!");
+ # my($line) = &readFile($fileName);
+ # splice(@$line, 5, 2);
+ # &writeFile($fileName, $line);
+
+ &heading('setTag');
+ $cvs -> setTag($newTag);
+
+ print "\n";
+
+ &getTags($cvs);
+
+ print "\n";
+
+} # End of setTag.
+
+#------------------------------------------------------------------
+
+sub strip
+{
+ my($cvs, $dirName) = @_;
+
+ &heading('stripCVSDirs');
+ $cvs -> stripCVSDirs($dirName);
+
+ print "\n";
+
+} # End of strip.
+
+#------------------------------------------------------------------
+
+sub upToDate
+{
+ my($cvs) = @_;
+
+ &heading('status');
+ my($status) = $cvs -> status();
+
+ print "Status: \n";
+ for (@$status)
+ {
+ print "$_\n";
+ }
+
+ print "\n";
+
+ &heading('upToDate');
+ my($upToDate) = $cvs -> upToDate();
+
+ print 'The repository is ', ($upToDate ? '' : 'not '), "up-to-date\n";
+ print "\n";
+
+} # End of upToDate.
+
+#------------------------------------------------------------------
+
+my($addDirMsg) = 'Add directory';
+my($addFileMsg) = 'Add file';
+my($dirName) = 'project';
+my($fileName) = fileparse($0, '');
+my($history) = 1;
+my($initialMsg) = 'Initial version';
+my($myself) = cwd() . "/$fileName";
+my($newTag) = 'release_0.01';
+my($noChange) = 1;
+my($nullTag) = '';
+my($permissions) = 0775; # But not '0775'!
+my($projectName) = 'project';
+my($projectSource) = 'projectSource';
+my($raw) = 0;
+my($readOnly) = 0;
+my($releaseTag) = 'release_0.00';
+my($removeFileMsg) = 'Remove file';
+my($repository) = 'repository';
+my($roDirName) = 'projectReadOnly';
+my($stripDirName) = 'projectStrip';
+my($subDirName) = 'subDir';
+my($vendorTag) = 'vendorTag';
+my($verbose) = 1;
+
+$ENV{'HOME'} = cwd();
+
+$ENV{'CVSROOT'} = "$ENV{'HOME'}/VCS-CVS-test/$repository";
+
+my($cvs) = VCS::CVS -> new({
+ 'project' => $projectName,
+ 'raw' => $raw,
+ 'history' => $history,
+ 'permissions' => $permissions,
+ 'verbose' => $verbose});
+
+&init($projectSource, $myself, $verbose, $permissions);
+
+chdir($ENV{'HOME'}) || die("Can't chdir($ENV{'HOME'}): $!");
+
+&createRepository($cvs, $projectSource, $vendorTag, $releaseTag, $initialMsg);
+
+&checkOut($cvs, $readOnly, $projectName, $nullTag);
+&checkOut($cvs, $readOnly, $stripDirName, $releaseTag);
+&checkOut($cvs, (! $readOnly), $roDirName, $releaseTag);
+
+&addDirectory($cvs, $projectName, $subDirName, $myself, $addDirMsg,
+ $addFileMsg, $verbose, $permissions);
+&addDirectory($cvs, $stripDirName, $subDirName, $myself, $addDirMsg,
+ $addFileMsg, $verbose, $permissions);
+
+#&setTag($cvs, $projectName, $fileName, $newTag);
+&setTag($cvs, $newTag);
+
+&upToDate($cvs);
+
+print "Update returned: \n", join("\n", @{$cvs -> update($noChange)}), "\n";
+print "\n";
+print "History returned: \n", join("\n", @{$cvs -> history({'-e' => ''})}), "\n";
+
+&strip($cvs, $stripDirName);
+
+# Success.
+exit(0);
diff --git a/cpan/lib/Rcs.pm b/cpan/lib/Rcs.pm
new file mode 100644
index 00000000..1c6e2f4b
--- /dev/null
+++ b/cpan/lib/Rcs.pm
@@ -0,0 +1,1343 @@
+package Rcs;
+require 5.001;
+use strict;
+use Carp;
+use Time::Local;
+use vars qw($VERSION $revision);
+
+#------------------------------------------------------------------
+# global stuff
+#------------------------------------------------------------------
+$VERSION = '0.09';
+$revision = '$Id: Rcs.pm,v 1.14.1.3 1998/10/19 18:04:48 freter Exp $';
+my $Dir_Sep = ($^O eq 'MSWin32') ? '\\' : '/';
+my $Exe_Ext = ($^O eq 'MSWin32') ? '.exe' : '';
+my $Rcs_Bin_Dir = '/usr/local/bin';
+my $Rcs_Dir = '.' . $Dir_Sep . 'RCS';
+my $Work_Dir = '.';
+my $Quiet = 1; # RCS quiet mode
+my $Arc_Ext = ',v';
+
+#------------------------------------------------------------------
+# RCS object constructor
+#------------------------------------------------------------------
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $self = {};
+
+ # provide default values for system stuff
+ $self->{"_BINDIR"} = \$Rcs_Bin_Dir;
+ $self->{"_QUIET"} = \$Quiet;
+ $self->{"_RCSDIR"} = \$Rcs_Dir;
+ $self->{"_WORKDIR"} = \$Work_Dir;
+ $self->{"_ARCEXT"} = \$Arc_Ext;
+
+ $self->{FILE} = undef;
+ $self->{ARCFILE} = undef;
+ $self->{AUTHOR} = undef;
+ $self->{COMMENTS} = undef;
+ $self->{DATE} = undef;
+ $self->{LOCK} = undef;
+ $self->{ACCESS} = [];
+ $self->{REVISIONS} = [];
+ $self->{REVINFO} = undef;
+ $self->{STATE} = undef;
+ $self->{SYMBOLS} = undef;
+ bless($self, $class);
+ return $self;
+}
+
+#------------------------------------------------------------------
+# access
+# Access list of archive file.
+#------------------------------------------------------------------
+sub access {
+ my $self = shift;
+
+ if (not @{ $self->{ACCESS} }) {
+ _parse_rcs_header($self);
+ }
+
+ # dereference revisions list
+ my @access = @{ $self->{ACCESS} };
+
+ return @access;
+}
+
+#------------------------------------------------------------------
+# arcext
+# Set the RCS archive file extension (default is ',v').
+#------------------------------------------------------------------
+sub arcext {
+ my $self = shift;
+
+ # called as object method
+ if (ref $self) {
+ if (@_) { ${ $self->{"_ARCEXT"} } = shift };
+ return ${ $self->{"_ARCEXT"} };
+ }
+
+ # called as class method
+ else {
+ if (@_) { $Arc_Ext = shift; }
+ return $Arc_Ext;
+ }
+}
+
+#------------------------------------------------------------------
+# arcfile
+# Name of RCS archive file.
+# If not set then return name of working file with RCS
+# extension (',v').
+#------------------------------------------------------------------
+sub arcfile {
+ my $self = shift;
+ if (@_) { $self->{ARCFILE} = shift }
+ return $self->{ARCFILE} || $self->{FILE} . ${ $self->{"_ARCEXT"} };
+}
+
+#------------------------------------------------------------------
+# author
+# Return the author of an RCS revision.
+# If revision is not provided, default to 'head' revision.
+#------------------------------------------------------------------
+sub author {
+ my $self = shift;
+
+ if (not defined $self->{AUTHOR}) {
+ _parse_rcs_header($self);
+ }
+ my $revision = shift || $self->{HEAD};
+
+ # dereference author hash
+ my %author_array = %{ $self->{AUTHOR} };
+
+ return $author_array{$revision};
+}
+
+#------------------------------------------------------------------
+# bindir
+# Set the bin directory in which the RCS distribution programs
+# reside.
+#------------------------------------------------------------------
+sub bindir {
+ my $self = shift;
+
+ # called as object method
+ if (ref $self) {
+ if (@_) { ${ $self->{"_BINDIR"} } = shift };
+ return ${ $self->{"_BINDIR"} };
+ }
+
+ # called as class method
+ else {
+ if (@_) { $Rcs_Bin_Dir = shift };
+ return $Rcs_Bin_Dir;
+ }
+}
+
+#------------------------------------------------------------------
+# ci
+# Execute RCS 'ci' program.
+# Make archive filename same as working filename unless
+# specifically set.
+#------------------------------------------------------------------
+sub ci {
+ my $self = shift;
+ my @param = @_;
+
+ my $ciprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'ci' . $Exe_Ext;
+ my $rcsdir = ${ $self->{"_RCSDIR"} };
+ my $workdir = ${ $self->{"_WORKDIR"} };
+ my $file = $self->{FILE};
+ my $arcfile = $self->{ARCFILE} || $file;
+
+ my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} };
+ my $workfile = $workdir . $Dir_Sep . $file;
+ push @param, $archive_file, $workfile;
+ unshift @param, "-q" if ${ $self->{"_QUIET"} }; # quiet mode
+
+ # run program
+ croak "ci program $ciprog not found" unless -e $ciprog;
+ croak "ci program $ciprog not executable" unless -x $ciprog;
+ system($ciprog, @param) == 0 or croak "$!";
+
+ # re-parse RCS file and clear comments hash
+ _parse_rcs_header($self);
+ $self->{COMMENTS} = undef;
+}
+
+#------------------------------------------------------------------
+# co
+# Execute RCS 'co' program.
+# Make archive filename same as working filename unless
+# specifically set.
+#------------------------------------------------------------------
+sub co {
+ my $self = shift;
+ my @param = @_;
+
+ my $coprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'co' . $Exe_Ext;
+ my $rcsdir = ${ $self->{"_RCSDIR"} };
+ my $workdir = ${ $self->{"_WORKDIR"} };
+ my $file = $self->{FILE};
+ my $arcfile = $self->{ARCFILE} || $file;
+
+ my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} };
+ my $workfile = $workdir . $Dir_Sep . $file;
+ push @param, $archive_file, $workfile;
+ unshift @param, "-q" if ${ $self->{"_QUIET"} }; # quiet mode
+
+ # run program
+ croak "co program $coprog not found" unless -e $coprog;
+ croak "co program $coprog not executable" unless -x $coprog;
+ system($coprog, @param) == 0 or croak "$!";
+
+ # re-parse RCS file and clear comments hash
+ _parse_rcs_header($self);
+ $self->{COMMENTS} = undef;
+}
+
+#------------------------------------------------------------------
+# comments
+#------------------------------------------------------------------
+sub comments {
+ my $self = shift;
+
+ if (not defined $self->{COMMENTS}) {
+ _parse_rcs_body($self);
+ }
+
+ return %{$self->{COMMENTS}};
+}
+
+#------------------------------------------------------------------
+# daterev
+#
+# Returns revisions which were created before a specified date.
+#
+# Method takes one or six arguments.
+#
+# If one argument, then argument is date number.
+#
+# If six arguments, then year (4 digit year), month (1-12), day
+# of month (1-31), hour (0-23), minute (0-59) and second (0-59).
+#------------------------------------------------------------------
+sub daterev {
+
+ my $self = shift;
+ my $target_time;
+
+ # validate arguments
+ unless (@_ == 1 or @_ == 6) {
+ croak "daterev must have either 1 or 6 arguments";
+ }
+
+ # string date passed
+ if (@_ == 6) {
+ my($year, $mon, $mday, $hour, $min, $sec) = @_;
+
+ if($year !~ /^\d{4}$/) {
+ croak "year (1st param) must be 4 digit number";
+ }
+
+ $mon--; # convert to 0-11 range
+ $target_time = timegm($sec, $min, $hour, $mday, $mon, $year);
+ }
+
+ # system date passed
+ else {
+ $target_time = shift;
+
+ if ($target_time !~ /^\d+$/) {
+ croak "system date must be an integer";
+ }
+ }
+
+ if (not defined $self->{DATE}) {
+ _parse_rcs_header($self);
+ }
+
+ my @revisions = ();
+ my %dates;
+ my %dates_hash = %{$self->{DATE}};
+
+ my $revision;
+ foreach $revision (keys %dates_hash) {
+ my $date = $dates_hash{$revision};
+ $dates{$date}{$revision} = 1;
+ }
+
+ my $date;
+ foreach $date (reverse sort keys %dates) {
+ foreach $revision (keys %{ $dates{$date} }) {
+ push @revisions, $revision if $date <= $target_time;
+ }
+ }
+
+ return wantarray ? @revisions : $revisions[0];
+}
+
+#------------------------------------------------------------------
+# dates
+# Return a hash of revision dates, keyed on revision, when called
+# in list mode.
+# Return the most recent date when called in scalar mode.
+#
+# RCS stores dates in GMT.
+# The date values are system dates.
+#------------------------------------------------------------------
+sub dates {
+ my $self = shift;
+
+ if (not defined $self->{DATE}) {
+ _parse_rcs_header($self);
+ }
+
+ my %DatesHash = %{$self->{DATE}};
+ my @dates_list = sort {$b<=>$a} values %DatesHash;
+ my $MostRecent = $dates_list[0];
+
+ return wantarray ? %DatesHash : $MostRecent;
+}
+
+#------------------------------------------------------------------
+# file
+# Name of working file.
+#------------------------------------------------------------------
+sub file {
+ my $self = shift;
+ if (@_) { $self->{FILE} = shift }
+ return $self->{FILE};
+}
+
+#------------------------------------------------------------------
+# head
+# Return the head revision.
+#------------------------------------------------------------------
+sub head {
+ my $self = shift;
+
+ if (not defined $self->{HEAD}) {
+ _parse_rcs_header($self);
+ }
+ return $self->{HEAD};
+}
+
+#------------------------------------------------------------------
+# lock
+# Return user who has file locked.
+#------------------------------------------------------------------
+sub lock {
+ my $self = shift;
+
+ if (not defined $self->{LOCK}) {
+ _parse_rcs_header($self);
+ }
+ return $self->{LOCK};
+}
+
+#------------------------------------------------------------------
+# quiet
+# Set or un-set RCS quiet mode.
+#------------------------------------------------------------------
+sub quiet {
+ my $self = shift;
+
+ # called as object method
+ if (ref $self) {
+
+ # set/un-set quiet mode
+ if (@_) {
+ my $mode = shift;
+ croak "Passed parameter must be either '0' or '1'"
+ unless $mode == 0 or $mode == 1;
+ ${ $self->{"_QUIET"} } = $mode;
+ return ${ $self->{"_QUIET"} };
+ }
+
+ # access quiet mode
+ else {
+ return ${ $self->{"_QUIET"} };
+ }
+ }
+
+ # called as class method
+ else {
+
+ # set/un-set quiet mode
+ if (@_) {
+ my $mode = shift;
+ croak "Passed parameter must be either '0' or '1'"
+ unless $mode == 0 or $mode == 1;
+ $Quiet = $mode;
+ return $Quiet;
+ }
+
+ # access quiet mode
+ else {
+ return $Quiet;
+ }
+ }
+}
+
+#------------------------------------------------------------------
+# rcs
+# Execute RCS 'rcs' program.
+# Make archive filename same as working filename unless
+# specifically set.
+#------------------------------------------------------------------
+sub rcs {
+ my $self = shift;
+ my @param = @_;
+
+ my $rcsprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'rcs' . $Exe_Ext;
+ my $rcsdir = ${ $self->{"_RCSDIR"} };
+ my $workdir = ${ $self->{"_WORKDIR"} };
+ my $file = $self->{FILE};
+ my $arcfile = $self->{ARCFILE} || $file;
+
+ my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} };
+ my $workfile = $workdir . $Dir_Sep . $file;
+ push @param, $archive_file, $workfile;
+ unshift @param, "-q" if ${ $self->{"_QUIET"} }; # quiet mode
+
+ # run program
+ croak "rcs program $rcsprog not found" unless -e $rcsprog;
+ croak "rcs program $rcsprog not executable" unless -x $rcsprog;
+ system($rcsprog, @param) == 0 or croak "$?";
+
+ # re-parse RCS file and clear comments hash
+ _parse_rcs_header($self);
+ $self->{COMMENTS} = undef;
+}
+
+#------------------------------------------------------------------
+# rcsclean
+# Execute RCS 'rcsclean' program.
+#------------------------------------------------------------------
+sub rcsclean {
+ my $self = shift;
+ my @param = @_;
+
+ my $rcscleanprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'rcsclean' . $Exe_Ext;
+ my $rcsdir = ${ $self->{"_RCSDIR"} };
+ my $workdir = ${ $self->{"_WORKDIR"} };
+ my $file = $self->{FILE};
+ my $arcfile = $self->{ARCFILE} || $file;
+
+ my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} };
+ my $workfile = $workdir . $Dir_Sep . $file;
+ push @param, $archive_file, $workfile;
+
+ # run program
+ croak "rcsclean program $rcscleanprog not found" unless -e $rcscleanprog;
+ croak "rcsclean program $rcscleanprog not executable" unless -x $rcscleanprog;
+ system($rcscleanprog, @param) == 0 or croak "$?";
+
+ # re-parse RCS file and clear comments hash
+ _parse_rcs_header($self);
+ $self->{COMMENTS} = undef;
+}
+
+#------------------------------------------------------------------
+# rcsdiff
+# Execute RCS 'rcsdiff' program.
+# Calling in list context returns the output of rcsdiff, while
+# calling in scalar context returns the return status of the
+# rcsdiff program.
+#------------------------------------------------------------------
+sub rcsdiff {
+ my $self = shift;
+ my @param = @_;
+
+ my $rcsdiff_prog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'rcsdiff' . $Exe_Ext;
+ my $rcsdir = ${ $self->{"_RCSDIR"} };
+ my $arcfile = $self->{ARCFILE} || $self->{FILE};
+ $arcfile = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} };
+ my $workfile = $self->workdir . $Dir_Sep . $self->file;
+
+ # un-taint parameter string
+ unshift @param, "-q" if ${ $self->{"_QUIET"} }; # quiet mode
+ my $param_str = join(' ', @param);
+ $param_str =~ s/([\w-]+)/$1/g;
+
+ croak "rcsdiff program $rcsdiff_prog not found" unless -e $rcsdiff_prog;
+ croak "rcsdiff program $rcsdiff_prog not executable" unless -x $rcsdiff_prog;
+ open(DIFF, "$rcsdiff_prog $param_str $arcfile $workfile |");
+ my @diff_output = <DIFF>;
+
+ # rcsdiff returns exit status 0 for no differences, 1 for differences,
+ # and 2 for error condition.
+ close DIFF;
+ my $status = $?;
+ croak "$rcsdiff_prog failed" if $status == 2;
+ return wantarray ? @diff_output : $status;
+}
+
+#------------------------------------------------------------------
+# rcsdir
+# Location of 'RCS' archive directory.
+#------------------------------------------------------------------
+sub rcsdir {
+ my $self = shift;
+
+ # called as object method
+ if (ref $self) {
+ if (@_) { ${ $self->{"_RCSDIR"} } = shift }
+ return ${ $self->{"_RCSDIR"} };
+ }
+
+ # called as class method
+ else {
+ if (@_) { $Rcs_Dir = shift }
+ return $Rcs_Dir;
+ }
+}
+
+#------------------------------------------------------------------
+# revdate
+# Return the revision date of an RCS revision.
+# If revision is not provided, default to 'head' revision.
+#
+# RCS stores dates in GMT. This method will return dates relative
+# to the local time zone.
+#------------------------------------------------------------------
+sub revdate {
+ my $self = shift;
+
+ if (not defined $self->{DATE}) {
+ _parse_rcs_header($self);
+ }
+ my $revision = shift || $self->{HEAD};
+
+ # dereference date hash
+ my %date_array = %{ $self->{DATE} };
+ my $date_str = $date_array{$revision};
+
+ return wantarray ? localtime($date_str) : $date_str;
+}
+
+#------------------------------------------------------------------
+# revisions
+#------------------------------------------------------------------
+sub revisions {
+ my $self = shift;
+
+ if (not @{ $self->{REVISIONS} }) {
+ _parse_rcs_header($self);
+ }
+
+ # dereference revisions list
+ my @revisions = @{ $self->{REVISIONS} };
+
+ @revisions;
+}
+
+#------------------------------------------------------------------
+# rlog
+# Execute RCS 'rlog' program.
+# Make archive filename same as working filename unless
+# specifically set.
+#------------------------------------------------------------------
+sub rlog {
+ my $self = shift;
+ my @param = @_;
+
+ my $rlogprog = ${ $self->{"_BINDIR"} } . $Dir_Sep . 'rlog' . $Exe_Ext;
+ my $rcsdir = ${ $self->{"_RCSDIR"} };
+ my $arcfile = $self->{ARCFILE} || $self->{FILE};
+
+ # un-taint parameter string
+ my $param_str = join(' ', @param);
+ $param_str =~ s/([\w-]+)/$1/g;
+
+ my $archive_file = $rcsdir . $Dir_Sep . $arcfile . ${ $self->{"_ARCEXT"} };
+ croak "rlog program $rlogprog not found" unless -e $rlogprog;
+ croak "rlog program $rlogprog not executable" unless -x $rlogprog;
+ open(RLOG, "$rlogprog $param_str $archive_file |");
+
+ my @logoutput = <RLOG>;
+ close RLOG;
+ croak "$rlogprog failed" if $?;
+ @logoutput;
+}
+
+#------------------------------------------------------------------
+# state
+# If revision is not provided, default to 'head' revision
+#------------------------------------------------------------------
+sub state {
+ my $self = shift;
+
+ if (not defined $self->{STATE}) {
+ _parse_rcs_header($self);
+ }
+ my $revision = shift || $self->{HEAD};
+
+ # dereference author hash
+ my %state_array = %{ $self->{STATE} };
+
+ return $state_array{$revision};
+}
+
+#------------------------------------------------------------------
+# symbol
+# If revision is not provided, default to 'head' revision
+#------------------------------------------------------------------
+sub symbol {
+ my $self = shift;
+
+ if (not defined $self->{SYMBOLS}) {
+ _parse_rcs_header($self);
+ }
+ my $revision = shift || $self->{HEAD};
+
+ # dereference symbols hash
+ my %sym_array = %{ $self->{SYMBOLS} };
+
+ return '' if not defined $sym_array{$revision};
+
+ my @symbols = @{ $sym_array{$revision} };
+
+ # return only first array element if user wants scalar
+ return wantarray ? @symbols : $symbols[0];
+}
+
+#------------------------------------------------------------------
+# symbols
+# Returns hash of all revisions keyed on symbol defined against file.
+#------------------------------------------------------------------
+sub symbols {
+ my $self = shift;
+
+ if(not defined $self->{SYMBOLS}) {
+ _parse_rcs_header($self);
+ }
+
+ my %symbols;
+
+ # loop through each revision
+ my $rev;
+ foreach $rev (@{ $self->{REVISIONS} }) {
+ my $sym;
+ foreach $sym (@{ $self->{SYMBOLS}->{$rev} }) {
+ $symbols{$sym} = $rev;
+ }
+ }
+ return %symbols;
+}
+
+#------------------------------------------------------------------
+# symrev
+# Returns the revision against which a specified symbol was
+# defined. If the symbol was not defined against any version
+# of this file, 0 is returned.
+#------------------------------------------------------------------
+sub symrev {
+ my $self = shift;
+ my $sym = shift;
+ if(! defined $sym) {
+ croak "You must supply a symbol to symrev";
+ }
+
+ if (not defined $self->{SYMBOLS}) {
+ _parse_rcs_header($self);
+ }
+
+ my $ret_rev = 0;
+ my %symbols;
+
+ # loop through each revision
+ my $rev;
+ REV_LOOP:
+ foreach $rev (@{ $self->{REVISIONS} }) {
+ # loop through each symbol defined against
+ # this revision
+ my $s;
+ foreach $s (@{ $self->{SYMBOLS}->{$rev} }) {
+
+ # store each revision matching the pattern
+ if (wantarray) {
+ $symbols{$s} = $rev if $s =~ /$sym/;
+ }
+
+ # if it's the one we're looking for, we can
+ # quit as we've found the revision we want
+ else {
+ if($s eq $sym) {
+ $ret_rev = $rev;
+ last REV_LOOP;
+ }
+ }
+ }
+ }
+
+ return wantarray ? %symbols : $ret_rev;
+}
+
+#------------------------------------------------------------------
+# workdir
+# Location of working directory.
+#------------------------------------------------------------------
+sub workdir {
+ my $self = shift;
+
+ # called as object method
+ if (ref $self) {
+ if (@_) { ${ $self->{"_WORKDIR"} } = shift }
+ return ${ $self->{"_WORKDIR"} };
+ }
+
+ # called as class method
+ else {
+ if (@_) { $Work_Dir = shift }
+ return $Work_Dir;
+ }
+}
+
+#------------------------------------------------------------------
+# _parse_rcs_body
+# Private function
+#------------------------------------------------------------------
+sub _parse_rcs_body {
+
+ my $self = shift;
+ local $_;
+
+ my %comments;
+
+ my $rcsdir = ${ $self->{"_RCSDIR"} };
+ my $file = $self->{FILE};
+ my $rcs_file = $rcsdir . $Dir_Sep . $file . ${ $self->{"_ARCEXT"} };
+
+ # parse RCS archive file
+ open RCS_FILE, $rcs_file or croak "Unable to open $rcs_file";
+
+ # skip header info and get description
+ DESC: while (<RCS_FILE>) {
+ if (/^desc$/) {
+ $comments{0} = '';
+ $_ = <RCS_FILE>; # read first line
+ s/^\@//; # remove leading '@'
+ while (1) {
+ last DESC if /^\@$/;
+ s/\@\@/\@/g; # RCS replaces single '@' with '@@'
+ $comments{0} .= $_;
+ $_ = <RCS_FILE>;
+ }
+ }
+ }
+
+ # parse revision comments
+ my $revision;
+ REVISION: while (<RCS_FILE>) {
+ if (/^[\d\.]+$/) {
+ chomp($revision = $_);
+ $_ = <RCS_FILE>;
+ if (/^log$/) {
+ $comments{$revision} = '';
+ $_ = <RCS_FILE>; # read first line
+ s/^\@//; # remove leading '@'
+ while (1) {
+ next REVISION if /^\@$/;
+ s/\@\@/\@/g; # RCS replaces single '@' with '@@'
+ $comments{$revision} .= $_;
+ $_ = <RCS_FILE>;
+ }
+ }
+ }
+ }
+
+ # loop through 'text' section to avoid capturing bogus info
+ continue {
+ if (/^text$/) { # 'text' tag should always be there, but check anyway
+ while (<RCS_FILE>) {
+ s/\@\@//g; # RCS replaces single '@' with '@@'
+ last if /\@$/
+ }
+ }
+ }
+
+ close RCS_FILE;
+ $self->{COMMENTS} = \%comments;
+}
+
+#------------------------------------------------------------------
+# _parse_rcs_header
+# Private function
+# Directly parse the RCS archive file.
+#------------------------------------------------------------------
+sub _parse_rcs_header {
+
+ my $self = shift;
+ local $_;
+
+ my ($head, $lock);
+ my (@access_list, @revisions);
+ my (%author, %date, %state, %symbols);
+
+ my $rcsdir = ${ $self->{"_RCSDIR"} };
+ my $file = $self->{FILE};
+ my $rcs_file = $rcsdir . $Dir_Sep . $file . ${ $self->{"_ARCEXT"} };
+
+ # parse RCS archive file
+ open RCS_FILE, $rcs_file
+ or croak "Unable to open $rcs_file";
+ while (<RCS_FILE>) {
+ next if /^\s*$/; # skip blank lines
+ last if /^desc$/; # end of header info
+
+ # get head revision
+ if (/^head\s/) {
+ ($head) = /^head\s+(.*?);$/;
+ next;
+ }
+
+ # get access list
+ if (/^access$/) {
+ while (<RCS_FILE>) {
+ chomp;
+ s/\s//g; # remove all whitespace
+ push @access_list, (split(/;/))[0];
+ last if /;$/;
+ }
+ next;
+ }
+
+ # get locker
+ # get symbols
+ if (/^symbols$/) {
+ while (<RCS_FILE>) {
+ chomp;
+ s/\s//g; # remove all whitespace
+ my ($sym, $rev) = split(/:/);
+ $rev =~ s/;$//;
+ push @{ $symbols{$rev} }, $sym;
+ last if /;$/;
+ }
+ next;
+ }
+
+ # get locker
+ if (/^locks/) {
+
+ # file not locked
+ if (/strict/) {
+ $lock = '';
+ next;
+ }
+
+ # get user who has file locked
+ my $next_line = <RCS_FILE>; # read next line
+ ($lock) = $next_line =~ m/^\s*(\w+):/;
+ next;
+ }
+
+ # get all revisions
+ if (/^\d+\.\d+/) {
+ chomp;
+ push @revisions, $_;
+
+ # get author, state and date of each revision
+ my $next_line = <RCS_FILE>;
+ chop(my $author = (split(/\s+/, $next_line))[3]);
+ chop(my $state = (split(/\s+/, $next_line))[5]);
+ chop(my $date = (split(/\s+/, $next_line))[1]);
+
+ # store date as date number
+ my ($year, $mon, $mday, $hour, $min, $sec) = split(/\./, $date);
+ $mon--; # convert to 0-11 range
+ my @date = ($sec,$min,$hour,$mday,$mon,$year);
+
+ # store value in hash using revision as key
+ $author{$_} = $author;
+ $state{$_} = $state;
+ $date{$_} = timegm(@date);
+ }
+ }
+ close RCS_FILE;
+
+ $self->{HEAD} = $head;
+ $self->{LOCK} = $lock;
+ $self->{ACCESS} = \@access_list;
+ $self->{REVISIONS} = \@revisions;
+ $self->{AUTHOR} = \%author;
+ $self->{DATE} = \%date;
+ $self->{STATE} = \%state;
+ $self->{SYMBOLS} = \%symbols;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Rcs - Perl Object Class for Revision Control System (RCS).
+
+=head1 SYNOPSIS
+
+ use Rcs;
+
+=head1 DESCRIPTION
+
+This Perl module provides an object oriented interface to access
+B<Revision Control System (RCS)> utilities. RCS must be installed on
+the system prior to using this module. This module should simplify
+the creation of an RCS front-end.
+
+=head2 OBJECT CONSTRUCTOR
+
+The B<new> method may be used as either a class method or an object
+method to create a new object.
+
+ # called as class method
+ $obj = Rcs->new;
+
+ # called as object method
+ $newobj = $obj->new;
+
+=head2 CLASS METHODS
+
+Besides the object constructor, there are three class methods provided
+which effect any newly created objects.
+
+The B<arcext> method sets the RCS archive extension, which is ',v' by
+default.
+
+ # set/unset RCS archive extension
+ Rcs->arcext(''); # set no archive extension
+ Rcs->arcext(',v'); # set archive extension to ',v'
+ $arc_ext = Rcs->arcext(); # get current archive extension
+
+The B<bindir> method sets the directory path where the RCS executables
+(i.e. rcs, ci, co) are located. The default location is '/usr/local/bin'.
+
+ # set RCS bin directory
+ Rcs->bindir('/usr/bin');
+
+ # access RCS bin directory
+ $bin_dir = Rcs->bindir;
+
+The B<quiet> method sets/unsets the quiet mode for the RCS executables.
+Quiet mode is set by default.
+
+ # set/unset RCS quiet mode
+ Rcs->quiet(0); # unset quiet mode
+ Rcs->quiet(1); # set quiet mode
+
+ # access RCS quiet mode
+ $quiet_mode = Rcs->quiet;
+
+These methods may also be called as object methods.
+
+ $obj->arcext('');
+ $obj->bindir('/usr/bin');
+ $obj->quiet(0);
+
+=head2 OBJECT ATTRIBUTE METHODS
+
+These methods set the attributes of the RCS object.
+
+The B<file> method is used to set the name of the RCS working file. The
+filename must be set before invoking any access of modifier methods on the
+object.
+
+ $obj->file('mr_anderson.pl');
+
+The B<arcfile> method is used to set the name of the RCS archive file.
+Using this method is optional, as the other methods will assume the archive
+filename is the same as the working file unless specified otherwise. The
+RCS archive extension (default ',v') is automatically added to the filename.
+
+ $obj->arcfile('principle_mcvicker.pl');
+
+The B<workdir> methods set the path of the RCS working directory. If not
+specified, default path is '.' (current working directory).
+
+ $obj->workdir('/usr/local/source');
+
+The B<rcsdir> methods set the path of the RCS archive directory. If not
+specified, default path is './RCS'.
+
+ $obj->rcsdir('/usr/local/archive');
+
+=head2 RCS PARSE METHODS
+
+This class provides methods to directly parse the RCS archive file.
+
+The B<access> method returns a list of all user on the access list.
+
+ @access_list = $obj->access;
+
+The B<author> method returns the author of the revision. The head revision
+is used if no revision argument is passed to method.
+
+ # returns the author of revision '1.3'
+ $author = $obj->author('1.3');
+
+ # returns the authos of the head revision
+ $author = $obj->author;
+
+The B<head> method returns the head revision.
+
+ $head = $obj->head;
+
+The B<lock> method returns the locker of the revision. The method returns
+null if the revision is unlocked. The head revision is used if no revision
+argument is passed to method.
+
+ # returns locker of revision '1.3'
+ $locker = $obj->lock('1.3');
+
+ # returns locker of head revision
+ $locker = $obj->lock;
+
+The B<revisions> method returns a list of all revisions of archive file.
+
+ @revisions = $obj->revisions;
+
+The B<state> method returns the state of the revision. The head revision
+is used if no revision argument is passed to method.
+
+ # returns state of revision '1.3'
+ $state = $obj->state('1.3');
+
+ # returns state of head revision
+ $state = $obj->state;
+
+The B<symbol> method returns the symbol(s) associated with a revision.
+If called in list context, method returns all symbols associated with
+revision. If called in scalar context, method returns last symbol
+assciated with a revision. The head revision is used if no revision argument
+is passed to method.
+
+ # list context, returns all symbols associated with revision 1.3
+ @symbols = $obj->symbol('1.3');
+
+ # list context, returns all symbols associated with head revision
+ @symbols = $obj->symbol;
+
+ # scalar context, returns last symbol associated with revision 1.3
+ $symbol = $obj->symbol('1.3');
+
+ # scalar context, returns last symbol associated with head revision
+ $symbol = $obj->symbol;
+
+The B<symbols> method returns a hash, keyed by symbol, of all of the revisions
+associated with the file.
+
+ %symbols = $obj->symbols;
+ foreach $sym (keys %symbols) {
+ $rev = $symbols{$sym};
+ }
+
+The B<revdate> method returns the date of a revision. The returned date format
+is the same as the localtime format. When called as a scalar, it returns the
+system date number. If called is list context, the list
+($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) is returned.
+
+ # scalar mode
+ $scalar_date = $obj->revdate;
+ print "Scalar date number = $scalar_date\n";
+ $date_str = localtime($scalar_date);
+ print "Scalar date string = $date_str\n";
+
+ # list mode
+ @list_date = $obj->revdate;
+ print "List date = @list_date\n";
+
+The B<dates> method returns a hash of revision dates, keyed on revision. The
+hash values are system date numbers. When called in scalar mode, the method
+returns the most recent revision date.
+
+ # list mode
+ %DatesHash = obj->dates;
+ @dates_list = sort {$b<=>$a} values %DatesHash;
+ $MostRecent = $dates_list[0];
+
+ # scalar mode
+ $most_recent = $obj->dates;
+ print "Most recent date = $most_recent\n";
+ $most_recent_str = localtime($most_recent);
+ print "Most recent date string = $most_recent_str\n";
+
+The B<symrev> method returns the revision against which a specified symbol was
+defined. If the symbol was not defined against any version of this file, 0 is
+returned.
+
+ # gets revision that has 'MY_SYMBOL' defined against it
+ $rev = $obj->symrev('MY_SYMBOL');
+
+The B<daterev> method returns revisions which were created before a specified
+date. Method may take one or six arguments. If one arguments is passed, then
+the argument is a date number. If six arguments are passed, then they represent
+a date string.
+
+ # one argument, date number
+ # gets revisions created before Sun Sep 6 22:23:47 1998
+ @revs = $obj->daterev(841436420);
+
+ # six argument
+ # gets revisions created before 25th June 1998 16:45:30
+ @revs = $obj->daterev(1998, 6, 25, 16, 45, 30);
+
+The B<comments> method returns a hash of revision comments, keyed on revision.
+A key value of 0 returns the description.
+
+ %comments = $obj->comments;
+ $description = $comments{0};
+ $comment_1_3 = $comments{'1.3'};
+
+=head2 RCS SYSTEM METHODS
+
+These methods invoke the RCS system utilities.
+
+The B<ci> method calls the RCS ci program.
+
+ # check in, and then check out in unlocked state
+ $obj->ci('-u');
+
+The B<co> method calls the RCS co program.
+
+ # check out in locked state
+ $obj->co('-l');
+
+The B<rcs> method calls the RCS rcs program.
+
+ # lock file
+ $obj->rcs('-l');
+
+The B<rcsdiff> method calls the RCS rcsdiff program. When called in
+list context, this method returns the outpout of the rcsdiff program.
+When called in scalar context, this method returns the return status of
+the rcsdiff program. The return status is 0 for the same, 1 for some
+differences, and 2 for error condition.
+
+When called without parameters, rcsdiff does a diff between the current
+working file, and the last revision checked in.
+
+ # call in list context
+ @diff_output = $obj->rcsdiff;
+
+ # call in scalar context
+ $changed = $obj->rcsdiff;
+ if ($changed) {
+ print "Working file has changed\n";
+ }
+
+Call rcsdiff with parameters to do a diff between any two revisions.
+
+ @diff_output = $obj->rcsdiff('-r1.2', '-r1.1');
+
+The B<rlog> method calls the RCS rlog program. This method returns the
+output of the rlog program.
+
+ # get complete log output
+ @rlog_complete = $obj->rlog;
+
+ # called with '-h' switch outputs only header information
+ @rlog_header = $obj->rlog('-h');
+ print @rlog_header;
+
+The B<rcsclean> method calls the RCS rcsclean program.
+
+ # remove working file
+ $obj->rcsclean;
+
+
+=head1 EXAMPLES
+
+=head2 CREATE ACCESS LIST
+
+Using method B<rcs> with the B<-a> switch allows you to add users to
+the access list of an RCS archive file.
+
+ use Rcs;
+ $obj = Rcs->new;
+
+ $obj->rcsdir("./project_tree/archive");
+ $obj->workdir("./project_tree/src");
+ $obj->file("cornholio.pl");
+
+Methos B<rcs> invokes the RCS utility rcs with the same parameters.
+
+ @users = qw(beavis butthead);
+ $obj->rcs("-a@users");
+
+Calling method B<access> returns list of users on access list.
+
+ $filename = $obj->file;
+ @access_list = $obj->access;
+ print "Users @access_list are on the access list of $filename\n";
+
+
+=head2 PARSE RCS ARCHIVE FILE
+
+Set class variables and create 'RCS' object.
+Set bin directory where RCS programs (e.g. rcs, ci, co) reside. The
+default is '/usr/local/bin'. This sets the bin directory for all objects.
+
+ use Rcs;
+ Rcs->bindir('/usr/bin');
+ $obj = Rcs->new;
+
+Set information regarding RCS object. This information includes name of the
+working file, directory of working file ('.' by default), and RCS archive
+directory ('./RCS' by default).
+
+ $obj->rcsdir("./project_tree/archive");
+ $obj->workdir("./project_tree/src");
+ $obj->file("cornholio.pl");
+
+ $head_rev = $obj->head;
+ $locker = $obj->lock;
+ $author = $obj->author;
+ @access = $obj->access;
+ @revisions = $obj->revisions;
+
+ $filename = $obj->file;
+
+ if ($locker) {
+ print "Head revision $head_rev is locked by $locker\n";
+ }
+ else {
+ print "Head revision $head_rev is unlocked\n";
+ }
+
+ if (@access) {
+ print "\nThe following users are on the access list of file $filename\n";
+ map { print "User: $_\n"} @access;
+ }
+
+ print "\nList of all revisions of $filename\n";
+ foreach $rev (@revisions) {
+ print "Revision: $rev\n";
+ }
+
+=head2 CHECK-IN FILE
+
+Set class variables and create 'RCS' object.
+Set bin directory where RCS programs (e.g. rcs, ci, co) reside. The
+default is '/usr/local/bin'. This sets the bin directory for all objects.
+
+ use Rcs;
+ Rcs->bindir('/usr/bin');
+ Rcs->quiet(0); # turn off quiet mode
+ $obj = Rcs->new;
+
+Set information regarding RCS object. This information includes name of
+working file, directory of working file ('.' by default), and RCS archive
+directory ('./RCS' by default).
+
+ $obj->file('cornholio.pl');
+
+ # Set RCS archive directory, is './RCS' by default
+ $obj->rcsdir("./project_tree/archive");
+
+ # Set working directory, is '.' by default
+ $obj->workdir("./project_tree/src");
+
+Check in file using B<-u> switch. This will check in the file, and will then
+check out the file in an unlocked state. The B<-m> switch is used to set the
+revision comment.
+
+Command:
+
+ $obj->ci('-u', '-mRevision Comment');
+
+is equivalent to commands:
+
+ $obj->ci('-mRevision Comment');
+ $obj->co;
+
+=head2 CHECK-OUT FILE
+
+Set class variables and create 'RCS' object.
+Set bin directory where RCS programs (e.g. rcs, ci, co) reside. The
+default is '/usr/local/bin'. This sets the bin directory for all objects.
+
+ use Rcs;
+ Rcs->bindir('/usr/bin');
+ Rcs->quiet(0); # turn off quiet mode
+ $obj = Rcs->new;
+
+Set information regarding RCS object. This information includes name of
+working file, directory of working file ('.' by default), and RCS archive
+directory ('./RCS' by default).
+
+ $obj->file('cornholio.pl');
+
+ # Set RCS archive directory, is './RCS' by default
+ $obj->rcsdir("./project_tree/archive");
+
+ # Set working directory, is '.' by default
+ $obj->workdir("./project_tree/src");
+
+Check out file read-only:
+
+ $obj->co;
+
+or check out and lock file:
+
+ $obj->co('-l');
+
+=head2 RCSDIFF
+
+Method B<rcsdiff> does an diff between revisions.
+
+ $obj = Rcs->new;
+ $obj->bindir('/usr/bin');
+
+ $obj->rcsdir("./project_tree/archive");
+ $obj->workdir("./project_tree/src");
+ $obj->file("cornholio.pl");
+
+ print "Diff of current working file\n";
+ if ($obj->rcsdiff) { # scalar context
+ print $obj->rcsdiff; # list context
+ }
+ else {
+ print "Versions are Equal\n";
+ }
+
+ print "\n\nDiff of revisions 1.2 and 1.1\n";
+ print $obj->rcsdiff('-r1.2', '-r1.1');
+
+=head2 RCSCLEAN
+
+Method B<rcsclean> will remove an unlocked working file.
+
+ use Rcs;
+ Rcs->bindir('/usr/bin');
+ Rcs->quiet(0); # turn off quiet mode
+ $obj = Rcs->new;
+
+ $obj->rcsdir("./project_tree/archive");
+ $obj->workdir("./project_tree/src");
+ $obj->file("cornholio.pl");
+
+ print "Quiet mode NOT set\n" unless Rcs->quiet;
+
+ $obj->rcsclean;
+
+=head1 AUTHOR
+
+Craig Freter, E<lt>F<craig@freter.com>E<gt>
+
+=head1 CONTRIBUTORS
+
+David Green, E<lt>F<greendjf@cvhp152.gpt.co.uk>E<gt>
+
+Jamie O'Shaughnessy, E<lt>F<jamie@thanatar.demon.co.uk>E<gt>
+
+=head1 COPYRIGHT
+
+Copyright (C) 1997,1998 Craig Freter. All rights reserved.
+This program is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
+
+=cut
+
diff --git a/cpan/lib/VCS/CVS.pm b/cpan/lib/VCS/CVS.pm
new file mode 100644
index 00000000..3204e1b3
--- /dev/null
+++ b/cpan/lib/VCS/CVS.pm
@@ -0,0 +1,1474 @@
+package VCS::CVS;
+
+# Name:
+# VCS::CVS.
+#
+# Documentation:
+# POD-style documentation is at the end. Extract it with pod2html.
+#
+# Tabs:
+# 4 spaces || die.
+#
+# --------------------------------------------------------------------------
+
+use strict;
+no strict 'refs';
+
+use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
+
+use Carp;
+use Cwd;
+use File::Find;
+use File::Path;
+
+require Exporter;
+
+@ISA = qw(Exporter);
+
+# Items to export into callers namespace by default. Note: do not export
+# names by default without a very good reason. Use EXPORT_OK instead.
+# Do not simply export all your public functions/methods/constants.
+
+@EXPORT = qw();
+
+@EXPORT_OK = qw();
+
+$VERSION = '2.00';
+
+# Preloaded methods go here.
+# --------------------------------------------------------------------------
+# Add an existing directory to the project.
+# $dir can be a full path, or relative to the CWD.
+
+sub addDirectory
+{
+ my($self, $dir, $subDir, $message) = @_;
+
+ # Preserve the caller's current working directory.
+ my($cwd) = cwd();
+ chdir($dir) || croak("Can't chdir($dir): \nFailure: $!");
+
+ # CVS options:
+ # -Q Really quiet.
+ # -m message Use this log message.
+ # $subDir Add this directory.
+
+ # Warning: Do not try to combine these lines under any circumstances...
+ # Perl can't handle null list elements in a call to system.
+ my(@args) = ('cvs');
+ push(@args, '-Q') if (! $self -> {'verbose'});
+ push(@args, 'add');
+
+ if ($message)
+ {
+ $message = '"' . $message . '"' if ($message !~ /^".*"$/);
+ push(@args, '-m', $message);
+ }
+
+ push(@args, $subDir);
+
+ $self -> runOrCroak(@args);
+
+ chdir($cwd) || croak("Can't chdir($cwd): $!");
+
+} # End of addDirectory.
+
+# --------------------------------------------------------------------------
+# Add an existing file to the project.
+# $dir can be a full path, or relative to the CWD.
+
+sub addFile
+{
+ my($self, $dir, $file, $message) = @_;
+
+ # Preserve the caller's current working directory.
+ my($cwd) = cwd();
+ chdir($dir) || croak("Can't chdir($dir): \nFailure: $!");
+
+ # CVS options:
+ # -Q Really quiet.
+ # -m message Use this log message.
+ # $file Add this file.
+
+ # Warning: Do not try to combine these lines under any circumstances...
+ # Perl can't handle null list elements in a call to system.
+ my(@args) = ('cvs');
+ push(@args, '-Q') if (! $self -> {'verbose'});
+ push(@args, 'add');
+
+ if ($message)
+ {
+ $message = '"' . $message . '"' if ($message !~ /^".*"$/);
+ push(@args, '-m', $message);
+ }
+
+ push(@args, $file);
+
+ $self -> runOrCroak(@args);
+
+ $self -> commit($message);
+
+ chdir($cwd) || croak("Can't chdir($cwd): $!");
+
+} # End of addFile.
+
+# --------------------------------------------------------------------------
+# Prepare & perform 'cvs checkout'.
+# You call checkOut, and it calls _checkOutDontCallMe.
+# $readOnly Interpretation
+# 0 Check out files as read-write
+# 1 Check out files as read-only
+# $tag Interpretation
+# Null Do not call upToDate; ie check out repository as is
+# ! Null Call upToDate; Croak if repository is not up-to-date
+# If you called new with $raw == 1, your tag is passed as is to CVS.
+# If you called new with $raw == 0, your tag is assumed to be of the
+# form release_1.23, and is converted to CVS's form release_1_23.
+# $dir can be a full path, or relative to the CWD.
+
+sub checkOut
+{
+ my($self, $readOnly, $tag, $dir) = @_;
+
+ $tag =~ s/([-a-zA-Z]+_\d\d?)\.(\d\d)/$1_$2/ if (! $self -> {'raw'});
+
+ $self -> _validateObject($self -> {'project'}, 'modules', 0);
+ $self -> _validateObject($tag, 'val-tags', 0);
+
+ croak("Failure: Move directory $dir out of the way") if (-d $dir);
+
+ # Ensure the repository is up-to-date.
+ croak("Failure: The repository is not up-to-date. Run 'cvs commit' or 'cvs update'")
+ if ($tag && (! $self -> upToDate() ) );
+
+ # Zap previous copy of work directory.
+ rmtree($dir, $self -> {'verbose'});
+
+ # Checkout a current copy of the project.
+ $self -> _checkOutDontCallMe($readOnly, $tag, $dir);
+
+} # End of checkOut.
+
+# --------------------------------------------------------------------------
+# Commit changes.
+# Called as appropriate by addFile, removeFile and removeDirectory,
+# so you don't need to call it.
+
+sub commit
+{
+ my($self, $message) = @_;
+
+ # CVS options:
+ # -Q Really quiet.
+ # -m message Use this log message.
+
+ # Warning: Do not try to combine these lines under any circumstances...
+ # Perl can't handle null list elements in a call to system.
+ my(@args) = ('cvs');
+ push(@args, '-Q') if (! $self -> {'verbose'});
+ push(@args, 'commit');
+
+ if ($message)
+ {
+ $message = '"' . $message . '"' if ($message !~ /^".*"$/);
+ push(@args, '-m', $message);
+ }
+
+ $self -> runOrCroak(@args);
+
+} # End of commit.
+
+# --------------------------------------------------------------------------
+# Create a repository, using the current $CVSROOT.
+
+sub createRepository
+{
+ my($self) = @_;
+
+ croak("Failure: Move directory $ENV{'CVSROOT'} out of the way") if (-d $ENV{'CVSROOT'});
+
+ # Create the repository and its files.
+ $self -> _mkpathOrCroak($ENV{'CVSROOT'});
+ $self -> _mkpathOrCroak("$ENV{'CVSROOT'}/CVSROOT");
+
+ # Create the modules file.
+ my(@args) = ();
+ push(@args, "CVSROOT\t\tCVSROOT");
+ push(@args, "modules\t\tCVSROOT\tmodules");
+ push(@args, "$self->{'project'}\t\t$self->{'project'}");
+
+ my($file) = "$ENV{'CVSROOT'}/CVSROOT/modules";
+ open(OUT, "> $file") || croak("Can't open($file): \nFailure: $!");
+ print OUT join("\n", @args), "\n";
+ close(OUT);
+
+ $file = "$ENV{'CVSROOT'}/CVSROOT/val-tags";
+ open(OUT, "> $file") || croak("Can't open($file): \nFailure: $!");
+ # Write nothing.
+ close(OUT);
+
+ if ($self -> {'history'})
+ {
+ $file = "$ENV{'CVSROOT'}/CVSROOT/history";
+ open(OUT, "> $file") || croak("Can't open($file): \nFailure: $!");
+ # Write nothing.
+ close(OUT);
+ }
+
+} # End of createRepository.
+
+# --------------------------------------------------------------------------
+# Return a reference to a list of tags.
+# See also: the $raw option to new().
+
+sub getTags
+{
+ my($self) = @_;
+
+ my($line) = [];
+
+ if (-e "$ENV{'CVSROOT'}/CVSROOT/val-tags")
+ {
+ $line = $self -> _readFile("$ENV{'CVSROOT'}/CVSROOT/val-tags");
+
+ for (@$line)
+ {
+ $_ = (split)[0];
+
+ # Convert tag_1_23 into tag_1.23, if requested.
+ s/([-a-zA-Z]+_\d\d?)_(\d\d)/$1\.$2/ if (! $self -> {'raw'});
+ }
+
+ }
+
+ $line;
+
+} # End of getTags.
+
+# --------------------------------------------------------------------------
+# Run cvs history [-options].
+# Return a reference to a list of lines.
+#
+# The default option is -c.
+
+sub history
+{
+ my($self, $optionRef) = @_;
+
+ # Preserve the caller's current working directory.
+ # cvs status only works on the whole repository when run from your project dir
+ # (assuming, of course, you've checked out into your home directory...).
+ my($cwd) = cwd();
+ chdir("$ENV{'HOME'}/$self->{'project'}") ||
+ croak("Can't chdir($ENV{'HOME'}/$self->{'project'}): $!");
+
+ # CVS history options:
+ # -c Report commits, ie -xARM.
+
+ if (ref($optionRef) ne 'HASH')
+ {
+ $optionRef = {'-c' => ''};
+ }
+
+ my(@args) = ('cvs');
+ push(@args, 'history');
+ push(@args, join(' ', %$optionRef) );
+ @args = `@args`;
+ chomp(@args);
+
+ chdir($cwd) || croak("Can't chdir($cwd): $!");
+
+ \@args;
+
+} # End of history.
+
+# --------------------------------------------------------------------------
+# These are the options in the anonymous hash of parameters you pass in to 'new'.
+#
+# 'project'
+# 'killerApp' The name of the project. No default
+#
+# 'history'
+# 0 Do not create $CVSROOT/CVSROOT/history when createRepository() is called. Default
+# 1 Create $CVSROOT/CVSROOT/history, which initiates 'cvs history' stuff
+#
+# 'permissions'
+# 0775 Unix-specific. Default. Do not use '0775'
+#
+# 'raw'
+# 0 Convert tags from CVS format to real format. Eg: release_1.23. Default
+# 1 Set/Get tags in raw CVS format. Eg: release_1_23
+#
+# 'verbose'
+# 0 Run quietly
+# 1 Report progress. Default
+
+sub new
+{
+ my($class, $optionRef) = @_;
+ $class = ref($class) || $class;
+ my($self) = (ref($optionRef) eq 'HASH') ? $optionRef : {};
+
+ my(%default) =
+ (
+ 'history' => 0,
+ 'permissions' => 0775, # But not '0775'!
+ 'project' => '',
+ 'raw' => 0,
+ 'verbose' => 1,
+ );
+
+ my($option);
+
+ for $option (keys(%default) )
+ {
+ $self -> {$option} = $default{$option} if (! defined($self -> {$option}) );
+ }
+
+ $ENV{'HOME'} = '' if (! defined($ENV{'HOME'}) );
+ $ENV{'CVSROOT'} = '' if (! defined($ENV{'CVSROOT'}) );
+
+ croak("Failure: No project name specified") if (! $self -> {'project'});
+ croak("Failure: Env. var HOME not set") if (! $ENV{'HOME'});
+ croak("Failure: Env. var CVSROOT not set") if (! $ENV{'CVSROOT'});
+
+ return bless $self, $class;
+
+} # End of new.
+
+# --------------------------------------------------------------------------
+# Import an existing directory structure. But, (sub) import is a reserved word.
+# Use this to populate a repository for the first time.
+# The value used for $vendorTag is not important; CVS discards it.
+# The value used to $releaseTag is important; CVS discards it (why?) but I
+# force it to be the first tag in $CVSROOT/CVSROOT/val-tags. Thus you
+# should supply a meaningful value. Thus 'release_0_00' is strongly, repeat
+# strongly, recommended.
+# If you called new with $raw == 1, $releaseTag is passed as is to CVS.
+# If you called new with $raw == 0, $releaseTag is assumed to be of the
+# form release_1.23, and is converted to CVS's form release_1_23.
+
+# $sourceDir can be a full path, or relative to the CWD.
+
+sub populate
+{
+ my($self, $sourceDir, $vendorTag, $releaseTag, $message) = @_;
+
+ $vendorTag = 'vendorTag' if ( ($#_ < 2) || (length($_[2]) == 0) );
+ $releaseTag = 'release_0_00' if ( ($#_ < 3) || (length($_[3]) == 0) );
+ $message = 'Initial version' if ($#_ < 4);
+
+ $releaseTag =~ s/([-a-zA-Z]+_\d\d?)\.(\d\d)/$1_$2/ if (! $self -> {'raw'});
+
+ # Preserve the caller's current working directory.
+ my($cwd) = cwd();
+ chdir($sourceDir) || croak("Can't chdir($sourceDir): \nFailure: $!");
+
+ # CVS options:
+ # -Q Really quiet.
+ # -m message Use this log message.
+
+ # Warning: Do not try to combine these lines under any circumstances...
+ # Perl can't handle null list elements in a call to system.
+ my(@args) = ('cvs');
+ push(@args, '-Q') if (! $self -> {'verbose'});
+ push(@args, 'import');
+
+ if ($message)
+ {
+ $message = '"' . $message . '"' if ($message !~ /^".*"$/);
+ push(@args, '-m', $message);
+ }
+
+ push(@args, $self -> {'project'}, $vendorTag, $releaseTag);
+
+ $self -> runOrCroak(@args);
+
+ chdir($cwd) || croak("Can't chdir($cwd): $!");
+
+ # Compensate for yet another CVS bug.
+ $self -> _fixTag($releaseTag);
+
+} # End of populate.
+
+# --------------------------------------------------------------------------
+# Remove a directory from the project.
+# This deletes the directory (and all its files) from your working copy
+# of the repository, as well as deleting them from the repository.
+# Warning: $dir will have $CVSROOT and $HOME prepended by this code.
+# Ie: $dir starts from - but excludes - your home directory
+# (assuming, of course, you've checked out into your home directory...).
+# You can't remove the current directory, or a parent thereof.
+
+sub removeDirectory
+{
+ my($self, $dir) = @_;
+
+ my($cvsDir) = "$ENV{'CVSROOT'}/$dir/";
+ my($workDir) = "$ENV{'HOME'}/$dir/";
+
+ # Preserve the caller's current working directory.
+ my($cwd) = cwd();
+
+ # Move into the work directory.
+ chdir($workDir) || croak("Can't chdir($workDir): \nFailure: $!");
+ my($thisCwd) = cwd();
+
+ # Sanity check.
+ croak("Failure: You can't remove the current directory, or a parent") if ($cwd =~ /^$thisCwd/);
+
+ # Ensure the repository is up-to-date.
+ croak("Failure: The repository is not up-to-date. Run 'cvs commit' or 'cvs update'")
+ if (! $self -> upToDate() );
+
+ # Read the CVS entries.
+ my($cvsEntries) = 'CVS/Entries';
+ my($entry) = $self -> _readFile($cvsEntries);
+
+ # Remove each file, using CVS.
+ for (@$entry)
+ {
+ next if (/^D/);
+
+ my($file);
+
+ $file = $1 if (/^\/(.+?)\//);
+
+ $self -> removeFile($workDir, $file, 'Whole directory removed');
+ }
+
+ $self -> commit('Whole directory removed');
+
+ # Move up, and remove the directory.
+ chdir('..') || croak("Can't chdir('..'): \nFailure: $!");
+ my($directory) = $workDir;
+ my($index) = rindex($directory, '/', (length($directory) - 2) );
+ substr($directory, 0, ($index + 1) ) = '';
+ rmtree($directory, $self -> {'verbose'});
+
+ # Edit the CVS entries file to remove the dir.
+ if (-f $cvsEntries)
+ {
+ $entry = $self -> _readFile($cvsEntries);
+ @$entry = grep(! /^D\/$directory\//, @$entry);
+ open(OUT, "> $cvsEntries") || croak("Can't open $cvsEntries: \nFailure: $!");
+ print OUT join("\n", @$entry), "\n";
+ close(OUT);
+ }
+
+ # Remove the directory from CVS.
+ rmtree($cvsDir, $self -> {'verbose'});
+
+ # Remove the directory from the modules list.
+ if ($dir !~ /\//)
+ {
+ $cvsEntries = "$ENV{'CVSROOT'}/CVSROOT/modules";
+ $entry = $self -> _readFile($cvsEntries);
+
+ my($i);
+
+ for ($i = 0; $i <= $#{$entry}; $i++)
+ {
+ my(@field) = split(/\s+/, $$entry[$i]);
+ splice(@$entry, $i, 1) if ($field[1] =~ /^$dir$/);
+ }
+
+ open(OUT, "> $cvsEntries") || croak("Can't open $cvsEntries: \nFailure: $!");
+ print OUT join("\n", @$entry), "\n";
+ close(OUT);
+ }
+
+ chdir($cwd) || croak("Can't chdir($cwd): $!");
+
+} # End of removeDirectory.
+
+# --------------------------------------------------------------------------
+# Remove a file from the project.
+# This deletes the file from your working copy of the repository,
+# as well as deleting it from the repository.
+# $dir can be a full path, or relative to the CWD.
+# $file is relative to $dir.
+
+sub removeFile
+{
+ my($self, $dir, $file, $message) = @_;
+
+ # Preserve the caller's current working directory.
+ my($cwd) = cwd();
+ chdir($dir) || croak("Can't chdir($dir): \nFailure: $!");
+
+ unlink($file) || croak("Can't unlink($file): $!");
+
+ # CVS options:
+ # -Q Really quiet.
+ # -f Remove the file first.
+ # -l Do not recurse.
+ # $file Checkout this module.
+
+ my(@args) = ('cvs');
+ push(@args, '-Q') if (! $self -> {'verbose'});
+ push(@args, 'remove', '-f', '-l', $file);
+
+ $self -> runOrCroak(@args);
+
+ $self -> commit($message);
+
+ chdir($cwd) || croak("Can't chdir($cwd): $!");
+
+} # End of removeFile.
+
+# --------------------------------------------------------------------------
+# The standard way to run a system command and report on the result.
+
+sub runOrCroak
+{
+ my($self, @args) = @_;
+
+ my($result) = 0xffff & system(@args);
+
+ print "Command: @args\n";
+
+ if ($result == 0)
+ {
+ print 'Success. ';
+ }
+ elsif ($result == 0xff00)
+ {
+ print "Failure: $!. ";
+ }
+ elsif ($result > 0x80)
+ {
+ $result >>= 8;
+ print "Exit status: $result. ";
+ }
+ else
+ {
+ if ($result & 0x80)
+ {
+ $result &= ~0x80;
+ print 'Coredump from ';
+ }
+
+ print "Signal $result. ";
+ }
+
+ printf("Result: %#04x\n", $result);
+
+ croak("Failure: Can't run '@args'") if ($result);
+
+} # End of runOrCroak.
+
+# --------------------------------------------------------------------------
+# Tag the repository.
+# You call setTag, and it calls _setTag.
+# If you called new with $raw == 1, your tag is passed as is to CVS.
+# If you called new with $raw == 0, your tag is assumed to be of the
+# form release_1.23, and is converted to CVS's form release_1_23.
+
+sub setTag
+{
+ my($self, $tag) = @_;
+
+ $tag =~ s/([-a-zA-Z]+_\d\d?)\.(\d\d)/$1_$2/ if (! $self -> {'raw'});
+
+ $self -> _validateObject($self -> {'project'}, 'modules', 0);
+ $self -> _validateObject($tag, 'val-tags', 1);
+
+ croak("Failure: The repository is not up-to-date. Run 'cvs commit' or 'cvs update'")
+ if ($self -> upToDate() == 0);
+
+ $self -> _setTag($tag);
+
+} # End of setTag.
+
+# --------------------------------------------------------------------------
+# Run cvs status.
+# Return a reference to a list of lines.
+# Only called by upToDate(), but you may call it.
+
+sub status
+{
+ my($self) = @_;
+
+ # Preserve the caller's current working directory.
+ # cvs status only works on the whole repository when run from your project dir
+ # (assuming, of course, you've checked out into your home directory...).
+ my($cwd) = cwd();
+ chdir("$ENV{'HOME'}/$self->{'project'}") ||
+ croak("Can't chdir($ENV{'HOME'}/$self->{'project'}): $!");
+
+ # CVS options:
+ # -Q Really quiet.
+
+ my(@args) = ('cvs');
+ push(@args, '-Q') if (! $self -> {'verbose'});
+ push(@args, 'status');
+ @args = `@args`;
+ chomp(@args);
+
+ chdir($cwd) || croak("Can't chdir($cwd): $!");
+
+ \@args;
+
+} # End of status.
+
+# --------------------------------------------------------------------------
+# Delete all CVS directories and files from a copy of the repository.
+
+sub stripCVSDirs
+{
+ my($self, $dir) = @_;
+
+ # Preserve the caller's current working directory.
+ my($cwd) = cwd();
+ chdir($dir) || croak("Can't chdir($dir): $!");
+
+ my(%dirStack);
+
+ find
+ (
+ sub
+ {
+ $dirStack{$File::Find::dir} = 1 if ($File::Find::dir =~ /\/CVS$/);
+ },
+ cwd()
+ );
+
+ for (keys(%dirStack) )
+ {
+ rmtree($_, $self -> {'verbose'});
+ }
+
+ chdir($cwd) || croak("Can't chdir($cwd): $!");
+
+} # End of stripCVSDirs.
+
+# --------------------------------------------------------------------------
+# Run cvs -q [-n] update.
+# Return a reference to a list of lines.
+# Each line will start with one of [UARMC?], as per the CVS docs.
+#
+# Parameters Interpretation
+# $n 0 -> Do not add -n to the cvs update command
+# 1 -> Add -n to the command
+
+sub update
+{
+ my($self, $n) = @_;
+
+ $n = 0 if (! defined($n) );
+
+ # Preserve the caller's current working directory.
+ # cvs status only works on the whole repository when run from your project dir
+ # (assuming, of course, you've checked out into your home directory...).
+ my($cwd) = cwd();
+ chdir("$ENV{'HOME'}/$self->{'project'}") ||
+ croak("Can't chdir($ENV{'HOME'}/$self->{'project'}): $!");
+
+ # CVS options:
+ # -q Quiet
+ # -n Do not change any files
+
+ my(@args) = ('cvs');
+ push(@args, '-q') if (! $self -> {'verbose'});
+ push(@args, '-n') if ($n);
+ push(@args, 'update');
+ @args = `@args`;
+ chomp(@args);
+
+ chdir($cwd) || croak("Can't chdir($cwd): $!");
+
+ \@args;
+
+} # End of update.
+
+# --------------------------------------------------------------------------
+# Return Interpretation
+# 0 Repository not up-to-date.
+# 1 Up-to-date.
+
+sub upToDate
+{
+ my($self) = @_;
+
+ # Get the status of the repository.
+ my($status) = $self -> status();
+ @$status = grep(/Status/ && ! /Up-to-date/, @$status);
+ my($result) = 1; # Up-to-date.
+ $result = 0 if ($#{$status} >= 0); # Not, because log contains something.
+
+ $result;
+
+} # End of upToDate.
+
+# --------------------------------------------------------------------------
+# Checkout a current copy of the project.
+# You call checkOut, and it calls this.
+
+sub _checkOutDontCallMe
+{
+ my($self, $readOnly, $tag, $dir) = @_;
+
+ # CVS options:
+ # -Q Really quiet.
+ # -r Read-only. Make the new working files read-only.
+ # -d$dir Use $dir, not $project, as the directory name.
+ # -r <tag> Check out files tagged with <tag>. Optional.
+ #
+ # $project Checkout this module.
+
+ # CVS bug. Remove trailing '/', if any.
+ $dir = $1 if ($dir =~ /^(.+)\/$/);
+
+ # Warning: Do not try to combine these lines under any circumstances...
+ # Perl can't handle null list elements in a call to system.
+ my(@args) = ('cvs');
+ push(@args, '-Q') if (! $self -> {'verbose'});
+ push(@args, '-r') if ($readOnly);
+ push(@args, 'checkout', '-A', '-P', "-d$dir");
+ push(@args, '-r', $tag) if ($tag);
+ push(@args, $self -> {'project'});
+
+ $self -> runOrCroak(@args);
+
+} # End of _checkOutDontCallMe.
+
+# --------------------------------------------------------------------------
+# Fix a tag CVS failed to add.
+# Warning: $tag must be in CVS format. Eg: release_1_23, not release_1.23.
+
+sub _fixTag
+{
+ my($self, $tag) = @_;
+
+ my($file) = "$ENV{'CVSROOT'}/CVSROOT/val-tags";
+
+ open(INX, $file) || croak("Can't open($file): \nFailure: $!");
+
+ my($found) = 0;
+
+ while (<INX>)
+ {
+ $found = 1 if (/^$tag/);
+ }
+
+ close(INX);
+
+ if (! $found)
+ {
+ print "Warning: CVS bug. Tag $tag not in file $file\n" if ($self -> {'verbose'});
+ print "Fixing... " if ($self -> {'verbose'});
+
+ open(OUT, ">> $file") || croak("Can't open(>>$file): \nFailure: $!");
+ print OUT "$tag y\n";
+ close(OUT);
+
+ print "Success\n" if ($self -> {'verbose'});
+ }
+
+} # End of _fixTag.
+
+# --------------------------------------------------------------------------
+
+sub _mkpathOrCroak
+{
+ my($self, $dir) = @_;
+
+ my($result) = mkpath($dir, $self -> {'verbose'}, $self -> {'permissions'});
+
+ croak("Can't mkpath($dir, $self->{'verbose'}, $self->{'permissions'}): \nFailure: $!")
+ if ( (! $result) && ($! !~ /No such file/) );
+
+} # End of _mkpathOrCroak.
+
+# --------------------------------------------------------------------------
+# Return a reference to a list of lines.
+
+sub _readFile
+{
+ my($self, $file) = @_;
+
+ open(INX, $file) || croak("Can't open($file): $!");
+ my(@line) = <INX>;
+ close(INX);
+ chomp(@line);
+
+ \@line;
+
+} # end of _readFile.
+
+# --------------------------------------------------------------------------
+# Tag the current version of the project.
+# Warning: $tag must be in CVS format. Eg: release_1_23, not release_1.23.
+# You call setTag and it calls this.
+
+sub _setTag
+{
+ my($self, $tag) = @_;
+
+ # Preserve the caller's current working directory.
+ # cvs tag only works on the whole repository when run from your project dir
+ # (assuming, of course, you've checked out into your home directory...).
+ my($cwd) = cwd();
+ chdir($ENV{'HOME'}) || croak("Can't chdir($ENV{'HOME'}): $!");
+
+ # CVS options:
+ # -Q Really quiet.
+ # -r <tag> Tag files with <tag>.
+ # $project Tag this module.
+
+ # Warning: Do not try to combine these lines under any circumstances...
+ # Perl can't handle null list elements in a call to system.
+ my(@args) = ('cvs');
+ push(@args, '-Q') if (! $self -> {'verbose'});
+ push(@args, 'tag', $tag, $self -> {'project'});
+
+ $self -> runOrCroak(@args);
+
+ chdir($cwd) || croak("Can't chdir($cwd): $!");
+
+ # Compensate for yet another CVS bug.
+ $self -> _fixTag($tag);
+
+} # End of _setTag.
+
+# --------------------------------------------------------------------------
+# Validate an entry in one of the CVS files 'module' or 'val-tags'.
+# Warning: $tag must be in CVS format. Eg: release_1_23, not release_1.23.
+
+sub _validateObject
+{
+ my($self, $tag, $file, $mustBeAbsent) = @_;
+
+ $file = "$ENV{'CVSROOT'}/CVSROOT/$file";
+
+ open(INX, $file) || croak("Can't open($file): \nFailure: $!");
+
+ my($found) = 0;
+
+ while (<INX>)
+ {
+ $found = 1 if (/^$tag/);
+ }
+
+ close(INX);
+
+ croak("Failure: Tag not found: $tag in file $file")
+ if ( (! $found) && (! $mustBeAbsent) );
+
+ croak("Failure: Tag already present: $tag in file $file")
+ if ($found && $mustBeAbsent);
+
+} # End of _validateObject.
+
+# --------------------------------------------------------------------------
+
+# Autoload methods go after =cut, and are processed by the autosplit program.
+
+1;
+
+__END__
+
+=head1 NAME
+
+C<VCS::CVS> - Provide a simple interface to CVS (the Concurrent Versions System).
+
+You need to be clear in your mind about the 4 directories involved:
+
+=over 4
+
+=item *
+
+The directory where your source code resides before you import it into CVS.
+It is used only once - during the import phase. Call this $projectSource.
+
+=item *
+
+The directory into which you check out a read-write copy of the repository,
+in order to edit that copy. Call this $project. You will spend up to 100% of
+your time working within this directory structure.
+
+=item *
+
+The directory in which the repository resides. This is $CVSROOT. Thus
+$projectSource will be imported into $CVSROOT/$project.
+
+=item *
+
+The directory into which you get a read-only copy of the repository, in order to,
+say, make and ship that copy. Call this $someDir. It must not be $project.
+
+=back
+
+Note: You cannot have a directory called CVS in your home directory. That's
+just asking for trouble.
+
+=head1 SYNOPSIS
+
+ #!/usr/gnu/bin/perl -w
+
+ use integer;
+ use strict;
+
+ use VCS::CVS;
+
+ my($history) = 1;
+ my($initialMsg) = 'Initial version';
+ my($noChange) = 1;
+ my($nullTag) = '';
+ my($permissions) = 0775; # But not '0775'!
+ my($project) = 'project';
+ my($projectSource) = 'projectSource';
+ my($raw) = 0;
+ my($readOnly) = 0;
+ my($releaseTag) = 'release_0.00';
+ my($vendorTag) = 'vendorTag';
+ my($verbose) = 1;
+
+ # Note the anonymous hash in the next line, new as of V 1.10.
+
+ my($cvs) = VCS::CVS -> new({
+ 'project' => $project,
+ 'raw' => $raw,
+ 'verbose' => $verbose,
+ 'permissions' => $permissions,
+ 'history' => $history});
+
+ $cvs -> createRepository();
+ $cvs -> populate($projectSource, $vendorTag, $releaseTag, $initialMsg);
+ $cvs -> checkOut($readOnly, $nullTag, $project);
+
+ print join("\n", @{$cvs -> update($noChange)});
+ print "\n";
+ print join("\n", @{$cvs -> history()});
+
+ exit(0);
+
+=head1 DESCRIPTION
+
+The C<VCS::CVS> module provides an OO interface to CVS.
+
+VCS - Version Control System - is the prefix given to each Perl module which
+deals with some sort of source code control system.
+
+I have seen CVS corrupt binary files, even when run with CVS's binary option -kb.
+So, since CVS doesn't support binary files, neither does VCS::CVS.
+
+Stop press: CVS V 1.10 (with RCS 5.7) supports binary files.
+
+Subroutines whose names start with a '_' are not normally called by you.
+
+There is a test program included, but I have not yet worked out exactly how to
+set it up for make test. Stay tuned.
+
+=head1 INSTALLATION
+
+You install C<VCS::CVS>, as you would install any perl module library,
+by running these commands:
+
+ perl Makefile.PL
+ make
+ make test
+ make install
+
+If you want to install a private copy of C<VCS::CVS> in your home
+directory, then you should try to produce the initial Makefile with
+something like this command:
+
+ perl Makefile.PL LIB=~/perl
+ or
+ perl Makefile.PL LIB=C:/Perl/Site/Lib
+
+If, like me, you don't have permission to write man pages into unix system
+directories, use:
+
+ make pure_install
+
+instead of make install. This option is secreted in the middle of p 414 of the
+second edition of the dromedary book.
+
+=head1 WARNING re CVS bugs
+
+The following are my ideas as to what constitutes a bug in CVS:
+
+=over 4
+
+=item *
+
+The initial revision tag, supplied when populating the repository with
+'cvs import', is not saved into $CVSROOT/CVSROOT/val-tags.
+
+=item *
+
+The 'cvs tag' command does not always put the tag into 'val-tags'.
+
+=item *
+
+C<'cvs checkout -dNameOfDir'> fails if NameOfDir =~ /\/$/.
+
+=item *
+
+C<'cvs checkout -d NameOfDir'> inserts a leading space into the name of
+the directory it creates.
+
+=back
+
+=head1 WARNING re test environment
+
+This code has only been tested under Unix. Sorry.
+
+=head1 WARNING re project names 'v' directory names
+
+I assume your copy of the repository was checked out into a directory with
+the same name as the project, since I do a 'cd $HOME/$project' before running
+'cvs status', to see if your copy is up-to-date. This is because some activity is
+forbibben unless your copy is up-to-date. Typical cases of this include:
+
+=over 4
+
+=item *
+
+C<checkOut>
+
+=item *
+
+C<removeDirectory>
+
+=item *
+
+C<setTag>
+
+=back
+
+=head1 WARNING re shell intervention
+
+Some commands cause the shell to become involved, which, under Unix, will read your
+.cshrc or whatever, which in turn may set CVSROOT to something other than what you
+set it to before running your script. If this happens, panic...
+
+Actually, I think I've eliminated such cases. You hope so.
+
+=head1 WARNING re Perl bug
+
+As always, be aware that these 2 lines mean the same thing, sometimes:
+
+=over 4
+
+=item *
+
+$self -> {'thing'}
+
+=item *
+
+$self->{'thing'}
+
+=back
+
+The problem is the spaces around the ->. Inside double quotes, "...", the
+first space stops the dereference taking place. Outside double quotes the
+scanner correctly associates the $self token with the {'thing'} token.
+
+I regard this as a bug.
+
+=head1 addDirectory($dir, $subDir, $message)
+
+Add an existing directory to the project.
+
+$dir can be a full path, or relative to the CWD.
+
+=head1 addFile($dir, $file, $message)
+
+Add an existing file to the project.
+
+$dir can be a full path, or relative to the CWD.
+
+=head1 checkOut($readOnly, $tag, $dir)
+
+Prepare & perform 'cvs checkout'.
+
+You call checkOut, and it calls _checkOutDontCallMe.
+
+=over 4
+
+=item *
+
+$readOnly == 0 -> Check out files as read-write.
+
+=item *
+
+$readOnly == 1 -> Check out files as read-only.
+
+=back
+
+=over 4
+
+=item *
+
+$tag is Null -> Do not call upToDate; ie check out repository as is.
+
+=item *
+
+$tag is not Null -> Call upToDate; Croak if repository is not up-to-date.
+
+=back
+
+The value of $raw used in the call to new influences the handling of $tag:
+
+=over 4
+
+=item *
+
+$raw == 1 -> Your tag is passed as is to CVS.
+
+=item *
+
+$raw == 0 -> Your tag is assumed to be of the form release_1.23, and is
+converted to CVS's form release_1_23.
+
+=back
+
+$dir can be a full path, or relative to the CWD.
+
+=head1 commit($message)
+
+Commit changes.
+
+Called as appropriate by addFile, removeFile and removeDirectory,
+so you don't need to call it.
+
+=head1 createRepository()
+
+Create a repository, using the current $CVSROOT.
+
+This involves creating these files:
+
+=over 4
+
+=item *
+
+$ENV{'CVSROOT'}/CVSROOT/modules
+
+=item *
+
+$ENV{'CVSROOT'}/CVSROOT/val-tags
+
+=item *
+
+$ENV{'CVSROOT'}/CVSROOT/history
+
+=back
+
+Notes:
+
+=over 4
+
+=item *
+
+The 'modules' file contains these lines:
+
+ CVSROOT CVSROOT
+ modules CVSROOT modules
+ $self -> {'project'} $self -> {'project'}
+
+where $self -> {'project'} comes from the 'project' parameter to new()
+
+=item *
+
+The 'val-tags' file is initially empty
+
+=item *
+
+The 'history' file is only created if the 'history' parameter to new() is set.
+The file is initially empty
+
+=back
+
+=head1 getTags()
+
+Return a reference to a list of tags.
+
+See also: the $raw option to new().
+
+C<getTags> does not take a project name because tags belong to the repository
+as a whole, not to a project.
+
+=head1 history({})
+
+Report details from the history log, $CVSROOT/CVSROOT/history.
+
+You must have used new({'history' => 1}), or some other mechanism, to create
+the history file, before CVS starts logging changes into the history file.
+
+The anonymous hash takes any parameters 'cvs history' takes, and joins them
+with a single space. Eg:
+
+ $cvs -> history();
+
+ $cvs -> history({'-e' => ''});
+
+ $cvs -> history({'-xARM' => ''});
+
+ $cvs -> history({'-u' => $ENV{'LOGNAME'}, '-x' => 'A'});
+
+but not
+
+ $cvs -> history({'-xA' => 'M'});
+
+because it doesn't work.
+
+=head1 new({})
+
+Create a new object. See the synopsis.
+
+The anonymous hash takes these parameters, of which 'project' is the
+only required one.
+
+=over 4
+
+=item *
+
+'project' => 'killerApp'. The required name of the project. No default
+
+=back
+
+=over 4
+
+=item *
+
+'permissions' => 0775. Unix-specific stuff. Default. Do not use '0775'.
+
+=back
+
+=over 4
+
+=item *
+
+'history' => 0. Do not create $CVSROOT/CVSROOT/history when createRepository() is called. Default
+
+=item *
+
+'history' => 1. Create $CVSROOT/CVSROOT/history, which initiates 'cvs history' stuff
+
+=back
+
+=over 4
+
+=item *
+
+'raw' => 0. Convert tags from CVS format to real format. Eg: release_1.23. Default.
+
+=item *
+
+'raw' => 1. Return tags in raw CVS format. Eg: release_1_23.
+
+=back
+
+=over 4
+
+=item *
+
+'verbose' => 0. Do not report on the progress of mkpath/rmtree
+
+=item *
+
+'verbose' => 1. Report on the progress of mkpath/rmtree. Default
+
+=back
+
+=head1 populate($sourceDir, $vendorTag, $releaseTag, $message)
+
+Import an existing directory structure. But, (sub) import is a reserved word.
+
+Use this to populate a repository for the first time.
+
+The value used for $vendorTag is not important; CVS discards it.
+
+The value used to $releaseTag is important; CVS discards it (why?) but I
+force it to be the first tag in $CVSROOT/CVSROOT/val-tags. Thus you
+should supply a meaningful value. Thus 'release_0_00' is strongly, repeat
+strongly, recommended.
+
+The value of $raw used in the call to new influences the handling of $tag:
+
+=over 4
+
+=item *
+
+$raw == 1 -> Your tag is passed as is to CVS.
+
+=item *
+
+$raw == 0 -> Your tag is assumed to be of the form release_1.23, and is
+converted to CVS's form release_1_23.
+
+=back
+
+=head1 removeDirectory($dir)
+
+Remove a directory from the project.
+
+This deletes the directory (and all its files) from your working copy
+of the repository, as well as deleting them from the repository.
+
+Warning: $dir will have $CVSROOT and $HOME prepended by this code.
+Ie: $dir starts from - but excludes - your home directory
+(assuming, of course, you've checked out into your home directory...).
+
+You can't remove the current directory, or a parent.
+
+=head1 removeFile($dir, $file, $message)
+
+Remove a file from the project.
+
+This deletes the file from your working copy of the repository,
+as well as deleting it from the repository.
+
+$dir can be a full path, or relative to the CWD.
+$file is relative to $dir.
+
+=head1 runOrCroak()
+
+The standard way to run a system command and report on the result.
+
+=head1 setTag($tag)
+
+Tag the repository.
+
+You call setTag, and it calls _setTag.
+
+The value of $raw used in the call to new influences the handling of $tag:
+
+=over 4
+
+=item *
+
+$raw == 1 -> Your tag is passed as is to CVS.
+
+=item *
+
+$raw == 0 -> Your tag is assumed to be of the form release_1.23, and is
+converted to CVS's form release_1_23.
+
+=back
+
+=head1 stripCVSDirs($dir)
+
+Delete all CVS directories and files from a copy of the repository.
+
+Each user directory contains a CVS sub-directory, which holds 3 files:
+
+=over 4
+
+=item *
+
+Entries
+
+=item *
+
+Repository
+
+=item *
+
+Root
+
+=back
+
+Zap 'em.
+
+=head1 status()
+
+Run cvs status.
+
+Return a reference to a list of lines.
+
+Only called by upToDate(), but you may call it.
+
+=head1 update($noChange)
+
+Run 'cvs C<-q> [C<-n>] update', returning a reference to a list of lines.
+Each line will start with one of [UARMC?], as per the CVS docs.
+
+$cvs -> update(1) is a good way to get a list of uncommited changes, etc.
+
+=over 4
+
+=item *
+
+$noChange == 0 -> Do not add C<-n> to the cvs command. Ie update your working copy
+
+=item *
+
+$noChange == 1 -> Add C<-n> to the cvs command. Do not change any files
+
+=back
+
+=head1 upToDate()
+
+=over 4
+
+=item *
+
+return == 0 -> Repository not up-to-date.
+
+=item *
+
+return == 1 -> Up-to-date.
+
+=back
+
+=head1 _checkOutDontCallMe($readOnly, $tag, $dir)
+
+Checkout a current copy of the project.
+
+You call checkOut, and it calls this.
+
+=over 4
+
+=item *
+
+$readOnly == 0 -> Check out files as read-write.
+
+=item *
+
+$readOnly == 1 -> Check out files as read-only.
+
+=back
+
+=head1 _fixTag($tag)
+
+Fix a tag which CVS failed to add.
+
+Warning: $tag must be in CVS format: release_1_23, not release_1.23.
+
+=head1 _mkpathOrCroak($self, $dir)
+
+There is no need for you to call this.
+
+=head1 _readFile($file)
+
+Return a reference to a list of lines.
+
+There is no need for you to call this.
+
+=head1 _setTag($tag)
+
+Tag the current version of the project.
+
+Warning: $tag must be in CVS format: release_1_23, not release_1.23.
+
+You call setTag and it calls this.
+
+=head1 _validateObject($tag, $file, $mustBeAbsent)
+
+Validate an entry in one of the CVS files 'module' or 'val-tags'.
+
+Warning: $tag must be in CVS format: release_1_23, not release_1.23.
+
+=head1 AUTHOR
+
+C<VCS::CVS> was written by Ron Savage I<E<lt>rpsavage@ozemail.com.auE<gt>> in 1998.
+
+=head1 LICENCE
+
+This program is free software; you can redistribute it and/or modify it under
+the same terms as Perl itself.