File Coverage

blib/lib/Rcs.pm
Criterion Covered Total %
statement 24 459 5.2
branch 2 224 0.8
condition 0 45 0.0
subroutine 8 40 20.0
pod 0 29 0.0
total 34 797 4.2


line stmt bran cond sub pod time code
1             package Rcs;
2             require 5.002;
3 1     1   6857 use strict;
  1         2  
  1         49  
4 1     1   6 use Exporter;
  1         2  
  1         51  
5 1     1   7 use Carp;
  1         15  
  1         92  
6 1     1   266418 use Time::Local;
  1         2244  
  1         73  
7 1     1   8 use vars qw($VERSION $revision);
  1         2  
  1         46  
8 1     1   1111 use subs qw(_rcsError);
  1         208  
  1         6  
9              
10             # Even though I don't really export anything, I use Exporter
11             # to look for 'nonFatal' 'Verbose' tags.
12 1     1   50 use vars qw(@ISA @EXPORT_OK);
  1         1  
  1         6395  
13             @ISA = qw(Exporter);
14             @EXPORT_OK = qw(nonFatal Verbose);
15              
16             #------------------------------------------------------------------
17             # global stuff
18             #------------------------------------------------------------------
19             $VERSION = '1.05';
20             $revision = '$Id: Rcs.pm,v 1.28 2003/12/12 00:53:34 freter Exp $';
21             my $Dir_Sep = ($^O eq 'MSWin32') ? '\\' : '/';
22             my $Exe_Ext = ($^O eq 'MSWin32') ? '.exe' : '';
23             my $Rcs_Bin_Dir = '/usr/local/bin';
24             my $Rcs_Dir = '.' . $Dir_Sep . 'RCS';
25             my $Work_Dir = '.';
26             my $Quiet = 1; # RCS quiet mode
27             my $nonFatal = 0; # default to fatal
28             my $Arc_Ext = ',v';
29              
30             #------------------------------------------------------------------
31             # RCS object constructor
32             #------------------------------------------------------------------
33             sub new {
34 0     0 0 0 my $proto = shift;
35 0   0     0 my $class = ref($proto) || $proto;
36 0         0 my $self = {};
37              
38             # provide default values for system stuff
39 0         0 $self->{"_BINDIR"} = \$Rcs_Bin_Dir;
40 0         0 $self->{"_QUIET"} = \$Quiet;
41 0         0 $self->{"_RCSDIR"} = \$Rcs_Dir;
42 0         0 $self->{"_WORKDIR"} = \$Work_Dir;
43 0         0 $self->{"_ARCEXT"} = \$Arc_Ext;
44              
45 0         0 $self->{FILE} = undef;
46 0         0 $self->{ARCFILE} = undef;
47 0         0 $self->{AUTHOR} = undef;
48 0         0 $self->{COMMENTS} = undef;
49 0         0 $self->{DATE} = undef;
50 0         0 $self->{LOCK} = undef;
51 0         0 $self->{ACCESS} = [];
52 0         0 $self->{REVISIONS} = [];
53 0         0 $self->{REVINFO} = undef;
54 0         0 $self->{STATE} = undef;
55 0         0 $self->{SYMBOLS} = undef;
56 0         0 bless($self, $class);
57              
58             # Allow user to pass archive file to object constructor
59             # Example: Rcs->new('RCS/diskio.c,v')
60 0 0       0 if (@_) {
61 0         0 $self->pathname(shift);
62             }
63              
64 0         0 return $self;
65             }
66              
67             #------------------------------------------------------------------
68             # Use import function to check for 'nonFatal' Tag.
69             #------------------------------------------------------------------
70             sub import {
71 1     1   13 my $pkg = shift;
72 1 50       5 $nonFatal = 1 if scalar grep /^nonFatal$/, @_;
73 1 50       2314 $Quiet = 0 if scalar grep /^Verbose$/, @_;
74             }
75              
76             #------------------------------------------------------------------
77             # access
78             # Access list of archive file.
79             #------------------------------------------------------------------
80             sub access {
81 0     0 0   my $self = shift;
82              
83 0 0         if (not @{ $self->{ACCESS} }) {
  0            
84 0           _parse_rcs_header($self);
85             }
86              
87             # dereference revisions list
88 0           my @access = @{ $self->{ACCESS} };
  0            
89              
90 0           return @access;
91             }
92              
93             #------------------------------------------------------------------
94             # arcext
95             # Set the RCS archive file extension (default is ',v').
96             #------------------------------------------------------------------
97             sub arcext {
98 0     0 0   my $self = shift;
99              
100             # called as object method
101 0 0         if (ref $self) {
102 0 0         if (@_) { $self->{"_ARCEXT"} = shift };
  0            
103 0 0         return ref $self->{"_ARCEXT"} ? ${ $self->{"_ARCEXT"} } : $self->{"_ARCEXT"};
  0            
104             }
105              
106             # called as class method
107             else {
108 0 0         if (@_) { $Arc_Ext = shift; }
  0            
109 0           return $Arc_Ext;
110             }
111             }
112              
113             #------------------------------------------------------------------
114             # arcfile
115             # Name of RCS archive file.
116             # If not set then return name of working file with RCS
117             # extension (',v').
118             #------------------------------------------------------------------
119             sub arcfile {
120 0     0 0   my $self = shift;
121 0 0         if (@_) { $self->{ARCFILE} = shift }
  0            
122 0   0       return $self->{ARCFILE} || $self->file . $self->arcext;
123             }
124              
125             #------------------------------------------------------------------
126             # author
127             # Return the author of an RCS revision.
128             # If revision is not provided, default to 'head' revision.
129             #------------------------------------------------------------------
130             sub author {
131 0     0 0   my $self = shift;
132              
133 0 0         if (not defined $self->{AUTHOR}) {
134 0           _parse_rcs_header($self);
135             }
136 0   0       my $revision = shift || $self->head;
137              
138             # dereference author hash
139 0           my %author_array = %{ $self->{AUTHOR} };
  0            
140              
141 0           return $author_array{$revision};
142             }
143              
144             #------------------------------------------------------------------
145             # bindir
146             # Set the bin directory in which the RCS distribution programs
147             # reside.
148             #------------------------------------------------------------------
149             sub bindir {
150 0     0 0   my $self = shift;
151              
152             # called as object method
153 0 0         if (ref $self) {
154 0 0         if (@_) { $self->{"_BINDIR"} = shift };
  0            
155 0 0         return ref $self->{"_BINDIR"} ? ${ $self->{"_BINDIR"} } : $self->{"_BINDIR"};
  0            
156             }
157              
158             # called as class method
159             else {
160 0 0         if (@_) { $Rcs_Bin_Dir = shift };
  0            
161 0           return $Rcs_Bin_Dir;
162             }
163             }
164              
165             #------------------------------------------------------------------
166             # ci
167             # Execute RCS 'ci' program.
168             # Make archive filename same as working filename unless
169             # specifically set.
170             #------------------------------------------------------------------
171             sub ci {
172 0     0 0   my $self = shift;
173 0           my @param = @_;
174              
175 0           my $ciprog = $self->bindir . $Dir_Sep . 'ci' . $Exe_Ext;
176 0           my $rcsdir = $self->rcsdir;
177 0           my $workdir = $self->workdir;
178 0           my $file = $self->file;
179 0           my $arcfile = $self->arcfile;
180              
181 0           my $archive_file = $rcsdir . $Dir_Sep . $arcfile;
182 0           my $workfile = $workdir . $Dir_Sep . $file;
183 0           push @param, $archive_file, $workfile;
184 0 0         unshift @param, "-q" if $self->quiet; # quiet mode
185              
186             # run program
187 0 0         return(_rcsError "ci program $ciprog not found") unless -e $ciprog;
188 0 0         return(_rcsError "ci program $ciprog not executable") unless -x $ciprog;
189 0 0         system($ciprog, @param) == 0 or return(_rcsError "$?");
190              
191             # re-parse RCS file and clear comments hash
192 0           _parse_rcs_header($self);
193 0           $self->{COMMENTS} = undef;
194 0           return 1;
195             }
196              
197             #------------------------------------------------------------------
198             # co
199             # Execute RCS 'co' program.
200             # Make archive filename same as working filename unless
201             # specifically set.
202             #------------------------------------------------------------------
203             sub co {
204 0     0 0   my $self = shift;
205 0           my @param = @_;
206              
207 0           my $coprog = $self->bindir . $Dir_Sep . 'co' . $Exe_Ext;
208 0           my $rcsdir = $self->rcsdir;
209 0           my $workdir = $self->workdir;
210 0           my $file = $self->file;
211 0           my $arcfile = $self->arcfile;
212              
213 0           my $archive_file = $rcsdir . $Dir_Sep . $arcfile;
214 0           my $workfile = $workdir . $Dir_Sep . $file;
215 0           push @param, $archive_file, $workfile;
216 0 0         unshift @param, "-q" if $self->quiet; # quiet mode
217              
218             # run program
219 0 0         return(_rcsError "co program $coprog not found") unless -e $coprog;
220 0 0         return(_rcsError "co program $coprog not executable") unless -x $coprog;
221 0 0         system($coprog, @param) == 0 or return(_rcsError "$?");
222              
223             # re-parse RCS file and clear comments hash
224 0           _parse_rcs_header($self);
225 0           $self->{COMMENTS} = undef;
226 0           return 1;
227             }
228              
229             #------------------------------------------------------------------
230             # comments
231             #------------------------------------------------------------------
232             sub comments {
233 0     0 0   my $self = shift;
234              
235 0 0         if (not defined $self->{COMMENTS}) {
236 0           _parse_rcs_body($self);
237             }
238              
239 0           return %{$self->{COMMENTS}};
  0            
240             }
241              
242             #------------------------------------------------------------------
243             # daterev
244             #
245             # Returns revisions which were created before a specified date.
246             #
247             # Method takes one or six arguments.
248             #
249             # If one argument, then argument is date number.
250             #
251             # If six arguments, then year (4 digit year), month (1-12), day
252             # of month (1-31), hour (0-23), minute (0-59) and second (0-59).
253             #------------------------------------------------------------------
254             sub daterev {
255              
256 0     0 0   my $self = shift;
257 0           my $target_time;
258              
259             # validate arguments
260 0 0 0       unless (@_ == 1 or @_ == 6) {
261 0           croak "daterev must have either 1 or 6 arguments";
262             }
263              
264             # string date passed
265 0 0         if (@_ == 6) {
266 0           my($year, $mon, $mday, $hour, $min, $sec) = @_;
267              
268 0 0         if($year !~ /^\d{4}$/) {
269 0           croak "year (1st param) must be 4 digit number";
270             }
271              
272 0           $mon--; # convert to 0-11 range
273 0           $target_time = timegm($sec, $min, $hour, $mday, $mon, $year);
274             }
275              
276             # system date passed
277             else {
278 0           $target_time = shift;
279              
280 0 0         if ($target_time !~ /^\d+$/) {
281 0           croak "system date must be an integer";
282             }
283             }
284              
285 0 0         if (not defined $self->{DATE}) {
286 0           _parse_rcs_header($self);
287             }
288              
289 0           my @revisions = ();
290 0           my %dates;
291 0           my %dates_hash = %{$self->{DATE}};
  0            
292              
293 0           my $revision;
294 0           foreach $revision (keys %dates_hash) {
295 0           my $date = $dates_hash{$revision};
296 0           $dates{$date}{$revision} = 1;
297             }
298              
299 0           my $date;
300 0           foreach $date (reverse sort keys %dates) {
301 0           foreach $revision (keys %{ $dates{$date} }) {
  0            
302 0 0         push @revisions, $revision if $date <= $target_time;
303             }
304             }
305              
306 0 0         return wantarray ? @revisions : $revisions[0];
307             }
308              
309             #------------------------------------------------------------------
310             # dates
311             # Return a hash of revision dates, keyed on revision, when called
312             # in list mode.
313             # Return the most recent date when called in scalar mode.
314             #
315             # RCS stores dates in GMT.
316             # The date values are system dates.
317             #------------------------------------------------------------------
318             sub dates {
319 0     0 0   my $self = shift;
320              
321 0 0         if (not defined $self->{DATE}) {
322 0           _parse_rcs_header($self);
323             }
324              
325 0           my %DatesHash = %{$self->{DATE}};
  0            
326 0           my @dates_list = sort {$b<=>$a} values %DatesHash;
  0            
327 0           my $MostRecent = $dates_list[0];
328              
329 0 0         return wantarray ? %DatesHash : $MostRecent;
330             }
331              
332             #------------------------------------------------------------------
333             # file
334             # Name of working file.
335             #------------------------------------------------------------------
336             sub file {
337 0     0 0   my $self = shift;
338 0 0         if (@_) { $self->{FILE} = shift }
  0            
339 0           return $self->{FILE};
340             }
341              
342             #------------------------------------------------------------------
343             # pathname
344             # Full name of working file, including path to it and RCS file extension.
345             # Sets the location of 'RCS' archive directory.
346             #------------------------------------------------------------------
347             sub pathname {
348              
349 0     0 0   my $self = shift;
350              
351 0 0         if (@_) {
352 0           my $filename = shift;
353 0 0         if ($filename =~ m/(.*)$Dir_Sep(.*)/) {
354 0           $self->rcsdir($1);
355 0           $filename = $2;
356             }
357             else {
358 0           $self->rcsdir('.');
359             }
360              
361             # Strip off archive extension if exists
362 0           my $arcext = $self->arcext;
363 0           $filename =~ s/$arcext$//;
364              
365 0           $self->file($filename);
366             }
367 0           return $self->rcsdir . $Dir_Sep . $self->file;
368             }
369              
370             #------------------------------------------------------------------
371             # head
372             # Return the head revision.
373             #------------------------------------------------------------------
374             sub head {
375 0     0 0   my $self = shift;
376              
377 0 0         if (not defined $self->{HEAD}) {
378 0           _parse_rcs_header($self);
379             }
380 0           return $self->{HEAD};
381             }
382              
383             #------------------------------------------------------------------
384             # lock
385             # Return user who has file locked.
386             #------------------------------------------------------------------
387             sub lock {
388 0     0 0   my $self = shift;
389              
390 0 0         if (not defined $self->{LOCK}) {
391 0           _parse_rcs_header($self);
392             }
393 0   0       my $revision = shift || $self->{HEAD};
394            
395 0 0         return wantarray ? %{ $self->{LOCK} } : ${ $self->{LOCK} }{$revision};
  0            
  0            
396             }
397              
398             #------------------------------------------------------------------
399             # quiet
400             # Set or un-set RCS quiet mode.
401             #------------------------------------------------------------------
402             sub quiet {
403 0     0 0   my $self = shift;
404              
405             # called as object method
406 0 0         if (ref $self) {
407              
408             # set/un-set quiet mode
409 0 0         if (@_) {
410 0           my $mode = shift;
411 0 0 0       croak "Passed parameter must be either '0' or '1'"
412             unless $mode == 0 or $mode == 1;
413 0           $self->{"_QUIET"} = $mode;
414 0 0         return ref $self->{"_QUIET"} ? ${ $self->{"_QUIET"} } : $self->{"_QUIET"};
  0            
415             }
416              
417             # access quiet mode
418             else {
419 0 0         return ref $self->{"_QUIET"} ? ${ $self->{"_QUIET"} } : $self->{"_QUIET"};
  0            
420             }
421             }
422              
423             # called as class method
424             else {
425              
426             # set/un-set quiet mode
427 0 0         if (@_) {
428 0           my $mode = shift;
429 0 0 0       croak "Passed parameter must be either '0' or '1'"
430             unless $mode == 0 or $mode == 1;
431 0           $Quiet = $mode;
432 0           return $Quiet;
433             }
434              
435             # access quiet mode
436             else {
437 0           return $Quiet;
438             }
439             }
440             }
441              
442             #------------------------------------------------------------------
443             # rcs
444             # Execute RCS 'rcs' program.
445             # Make archive filename same as working filename unless
446             # specifically set.
447             #------------------------------------------------------------------
448             sub rcs {
449 0     0 0   my $self = shift;
450 0           my @param = @_;
451              
452 0           my $rcsprog = $self->bindir . $Dir_Sep . 'rcs' . $Exe_Ext;
453 0           my $rcsdir = $self->rcsdir;
454 0           my $workdir = $self->workdir;
455 0           my $file = $self->file;
456 0           my $arcfile = $self->arcfile;
457              
458 0           my $archive_file = $rcsdir . $Dir_Sep . $arcfile;
459 0           my $workfile = $workdir . $Dir_Sep . $file;
460 0           push @param, $archive_file, $workfile;
461 0 0         unshift @param, "-q" if $self->quiet; # quiet mode
462              
463             # run program
464 0 0         return(_rcsError "rcs program $rcsprog not found") unless -e $rcsprog;
465 0 0         return(_rcsError "rcs program $rcsprog not executable") unless -x $rcsprog;
466 0 0         system($rcsprog, @param) == 0 or return(_rcsError "$?");
467              
468             # re-parse RCS file and clear comments hash
469 0           _parse_rcs_header($self);
470 0           $self->{COMMENTS} = undef;
471 0           return 1;
472             }
473              
474             #------------------------------------------------------------------
475             # rcsclean
476             # Execute RCS 'rcsclean' program.
477             #------------------------------------------------------------------
478             sub rcsclean {
479 0     0 0   my $self = shift;
480 0           my @param = @_;
481              
482 0           my $rcscleanprog = $self->bindir . $Dir_Sep . 'rcsclean' . $Exe_Ext;
483 0           my $rcsdir = $self->rcsdir;
484 0           my $workdir = $self->workdir;
485 0           my $file = $self->file;
486 0           my $arcfile = $self->arcfile;
487              
488 0           my $archive_file = $rcsdir . $Dir_Sep . $arcfile;
489 0           my $workfile = $workdir . $Dir_Sep . $file;
490 0           push @param, $archive_file, $workfile;
491              
492             # run program
493 0 0         return(_rcsError "rcsclean program $rcscleanprog not found") unless -e $rcscleanprog;
494 0 0         return(_rcsError "rcsclean program $rcscleanprog not executable") unless -x $rcscleanprog;
495 0 0         system($rcscleanprog, @param) == 0 or return(_rcsError "$?");
496              
497             # re-parse RCS file and clear comments hash
498 0           _parse_rcs_header($self);
499 0           $self->{COMMENTS} = undef;
500 0           return 1;
501             }
502              
503             #------------------------------------------------------------------
504             # rcsdiff
505             # Execute RCS 'rcsdiff' program.
506             # Calling in list context returns the output of rcsdiff, while
507             # calling in scalar context returns the return status of the
508             # rcsdiff program.
509             #------------------------------------------------------------------
510             sub rcsdiff {
511 0     0 0   my $self = shift;
512 0           my @param = @_;
513              
514 0           my $rcsdiff_prog = $self->bindir . $Dir_Sep . 'rcsdiff' . $Exe_Ext;
515 0           my $rcsdir = $self->rcsdir;
516 0           my $arcfile = $self->arcfile;
517 0           $arcfile = $rcsdir . $Dir_Sep . $arcfile;
518 0           my $workfile = $self->workdir . $Dir_Sep . $self->file;
519              
520             # un-taint parameter string
521 0 0         unshift @param, "-q" if $self->quiet; # quiet mode
522 0           my $param_str = join(' ', @param);
523 0           $param_str =~ s/([\w-]+)/$1/g;
524              
525 0 0         return(_rcsError "rcsdiff program $rcsdiff_prog not found")
526             unless -e $rcsdiff_prog;
527 0 0         return(_rcsError "rcsdiff program $rcsdiff_prog not executable")
528             unless -x $rcsdiff_prog;
529 0 0         open(DIFF, "$rcsdiff_prog $param_str $arcfile $workfile |")
530             or return(_rcsError "Can't fork $rcsdiff_prog: $!");
531 0           my @diff_output = ;
532              
533             # rcsdiff returns exit status 0 for no differences, 1 for differences,
534             # and 2 for error condition.
535 0           close DIFF;
536 0           my $status = $?;
537 0           $status >>= 8;
538 0 0         return(_rcsError "$rcsdiff_prog failed") if $status == 2;
539 0 0         return wantarray ? @diff_output : $status;
540             }
541              
542             #------------------------------------------------------------------
543             # rcsdir
544             # Location of 'RCS' archive directory.
545             #------------------------------------------------------------------
546             sub rcsdir {
547 0     0 0   my $self = shift;
548              
549             # called as object method
550 0 0         if (ref $self) {
551 0 0         if (@_) { $self->{"_RCSDIR"} = shift }
  0            
552 0 0         return ref $self->{"_RCSDIR"} ? ${ $self->{"_RCSDIR"} } : $self->{"_RCSDIR"};
  0            
553             }
554              
555             # called as class method
556             else {
557 0 0         if (@_) { $Rcs_Dir = shift }
  0            
558 0           return $Rcs_Dir;
559             }
560             }
561              
562             #------------------------------------------------------------------
563             # revdate
564             # Return the revision date of an RCS revision.
565             # If revision is not provided, default to 'head' revision.
566             #
567             # RCS stores dates in GMT. This method will return dates relative
568             # to the local time zone.
569             #------------------------------------------------------------------
570             sub revdate {
571 0     0 0   my $self = shift;
572              
573 0 0         if (not defined $self->{DATE}) {
574 0           _parse_rcs_header($self);
575             }
576 0   0       my $revision = shift || $self->head;
577              
578             # dereference date hash
579 0           my %date_array = %{ $self->{DATE} };
  0            
580 0           my $date_str = $date_array{$revision};
581              
582 0 0         return wantarray ? localtime($date_str) : $date_str;
583             }
584              
585             #------------------------------------------------------------------
586             # revisions
587             #------------------------------------------------------------------
588             sub revisions {
589 0     0 0   my $self = shift;
590              
591 0 0         if (not @{ $self->{REVISIONS} }) {
  0            
592 0           _parse_rcs_header($self);
593             }
594              
595             # dereference revisions list
596 0           my @revisions = @{ $self->{REVISIONS} };
  0            
597              
598 0           @revisions;
599             }
600              
601             #------------------------------------------------------------------
602             # rlog
603             # Execute RCS 'rlog' program.
604             # Make archive filename same as working filename unless
605             # specifically set.
606             #------------------------------------------------------------------
607             sub rlog {
608 0     0 0   my $self = shift;
609 0           my @param = @_;
610              
611 0           my $rlogprog = $self->bindir . $Dir_Sep . 'rlog' . $Exe_Ext;
612 0           my $rcsdir = $self->rcsdir;
613 0   0       my $arcfile = $self->arcfile || $self->file;
614              
615             # un-taint parameter string
616 0           my $param_str = join(' ', @param);
617 0           $param_str =~ s/([\w-]+)/$1/g;
618              
619 0           my $archive_file = $rcsdir . $Dir_Sep . $arcfile;
620 0 0         return(_rcsError "rlog program $rlogprog not found") unless -e $rlogprog;
621 0 0         return(_rcsError "rlog program $rlogprog not executable") unless -x $rlogprog;
622 0 0         open(RLOG, "$rlogprog $param_str $archive_file |")
623             or return(_rcsError "Can't fork $rlogprog: $!");
624              
625 0           my @logoutput = ;
626 0           close RLOG;
627 0 0         return(_rcsError "$rlogprog failed") if $?;
628 0           @logoutput;
629             }
630              
631             #------------------------------------------------------------------
632             # rcsmerge
633             # Execute RCS 'rcsmerge' program.
634             #------------------------------------------------------------------
635             sub rcsmerge {
636 0     0 0   my $self = shift;
637 0           my @param = @_;
638              
639 0           my $rcsmergeprog = $self->bindir . $Dir_Sep . 'rcsmerge' . $Exe_Ext;
640 0           my $rcsdir = $self->rcsdir;
641 0   0       my $arcfile = $self->arcfile || $self->file;
642              
643             # un-taint parameter string
644 0           my $param_str = join(' ', @param);
645 0           $param_str =~ s/([\w-]+)/$1/g;
646              
647 0           my $archive_file = $rcsdir . $Dir_Sep . $arcfile;
648 0 0         return(_rcsError "rcsmerge program $rcsmergeprog not found") unless -e $rcsmergeprog;
649 0 0         return(_rcsError "rcsmerge program $rcsmergeprog not executable") unless -x $rcsmergeprog;
650 0 0         open(RCSMERGE, "$rcsmergeprog $param_str $archive_file |")
651             or return(_rcsError "Can't fork $rcsmergeprog $!");
652              
653 0           my @logoutput = ;
654 0           close RCSMERGE;
655 0 0         return(_rcsError "$rcsmergeprog failed") if $?;
656 0           @logoutput;
657             }
658              
659             #------------------------------------------------------------------
660             # state
661             # If revision is not provided, default to 'head' revision
662             #------------------------------------------------------------------
663             sub state {
664 0     0 0   my $self = shift;
665              
666 0 0         if (not defined $self->{STATE}) {
667 0           _parse_rcs_header($self);
668             }
669 0   0       my $revision = shift || $self->head;
670              
671             # dereference author hash
672 0           my %state_array = %{ $self->{STATE} };
  0            
673              
674 0           return $state_array{$revision};
675             }
676              
677             #------------------------------------------------------------------
678             # symbol
679             # Return symbol(s) based on revision.
680             #------------------------------------------------------------------
681             sub symbol {
682 0     0 0   my $self = shift;
683              
684 0           my $sym;
685             my @sym_array;
686              
687 0 0         if (not defined $self->{SYMBOLS}) {
688 0           _parse_rcs_header($self);
689             }
690 0   0       my $revision = shift || $self->head;
691              
692             # dereference symbols hash
693 0           my %symbols = %{ $self->{SYMBOLS} };
  0            
694              
695 0           foreach $sym (keys %symbols) {
696 0           my $rev = $symbols{$sym};
697 0 0         push @sym_array, $sym if $rev eq $revision;
698             }
699              
700             # return only first array element if user wants scalar
701 0 0         return wantarray ? @sym_array : $sym_array[0];
702             }
703              
704             #------------------------------------------------------------------
705             # symbols
706             # Returns hash of all revisions keyed on symbol defined against file.
707             #------------------------------------------------------------------
708             sub symbols {
709 0     0 0   my $self = shift;
710              
711 0 0         if(not defined $self->{SYMBOLS}) {
712 0           _parse_rcs_header($self);
713             }
714              
715 0           return %{$self->{SYMBOLS}};
  0            
716             }
717              
718             #------------------------------------------------------------------
719             # symrev
720             # Returns the revision against which a specified symbol was
721             # defined. If the symbol was not defined against any version
722             # of this file, 0 is returned.
723             #------------------------------------------------------------------
724             sub symrev {
725 0     0 0   my $self = shift;
726 0 0         my $sym = shift or croak "You must supply a symbol to symrev";;
727              
728 0 0         if (not defined $self->{SYMBOLS}) {
729 0           _parse_rcs_header($self);
730             }
731              
732 0           my %symbols = %{ $self->{SYMBOLS} };
  0            
733 0 0         my $revision = $symbols{$sym} ? $symbols{$sym} : 0;
734              
735 0           my %matched_symbols = map { $_ => $symbols{$_} } grep(/$sym/, keys %symbols);
  0            
736              
737 0 0         return wantarray ? %matched_symbols : $revision;
738             }
739              
740             #------------------------------------------------------------------
741             # workdir
742             # Location of working directory.
743             #------------------------------------------------------------------
744             sub workdir {
745 0     0 0   my $self = shift;
746              
747             # called as object method
748 0 0         if (ref $self) {
749 0 0         if (@_) { $self->{"_WORKDIR"} = shift }
  0            
750 0 0         return ref $self->{"_WORKDIR"} ? ${ $self->{"_WORKDIR"} } : $self->{"_WORKDIR"};
  0            
751             }
752              
753             # called as class method
754             else {
755 0 0         if (@_) { $Work_Dir = shift }
  0            
756 0           return $Work_Dir;
757             }
758             }
759              
760             #------------------------------------------------------------------
761             # _parse_rcs_body
762             # Private function
763             #------------------------------------------------------------------
764             sub _parse_rcs_body {
765              
766 0     0     my $self = shift;
767 0           local $_;
768              
769 0           my %comments;
770              
771 0           my $rcsdir = $self->rcsdir;
772 0           my $file = $self->file;
773 0           my $rcs_file = $rcsdir . $Dir_Sep . $file . $self->arcext;
774              
775             # parse RCS archive file
776 0 0         open RCS_FILE, $rcs_file
777             or return(_rcsError "Unable to open $rcs_file: $!");
778              
779             # skip header info and get description
780 0           DESC: while () {
781 0 0         if (/^desc$/) {
782 0           $comments{0} = '';
783 0           $_ = ; # read first line
784 0           s/^\@//; # remove leading '@'
785 0           while (1) {
786 0 0         last DESC if /^\@$/;
787 0           s/\@\@/\@/g; # RCS replaces single '@' with '@@'
788 0           $comments{0} .= $_;
789 0           $_ = ;
790             }
791             }
792             }
793              
794             # parse revision comments
795 0           my $revision;
796 0           REVISION: while () {
797 0 0         if (/^[\d\.]+$/) {
798 0           chomp($revision = $_);
799 0           $_ = ;
800 0 0         if (/^log$/) {
801 0           $comments{$revision} = '';
802 0           $_ = ; # read first line
803 0           s/^\@//; # remove leading '@'
804 0           while (1) {
805 0 0         next REVISION if /^\@$/;
806 0           s/\@\@/\@/g; # RCS replaces single '@' with '@@'
807 0           $comments{$revision} .= $_;
808 0           $_ = ;
809             }
810             }
811             }
812             }
813              
814             # loop through 'text' section to avoid capturing bogus info
815             continue {
816 0 0         if (/^text$/) { # 'text' tag should always be there, but check anyway
817 0           $_ = ; # read first line
818 0 0         if (not /^\@\@$/) { # forced revisions have single '@@' in text section
819 0           while () {
820 0           s/\@\@//g; # RCS replaces single '@' with '@@'
821 0 0         last if /\@$/
822             }
823             }
824             }
825             }
826              
827 0           close RCS_FILE;
828 0           $self->{COMMENTS} = \%comments;
829             }
830              
831             #------------------------------------------------------------------
832             # _parse_rcs_header
833             # Private function
834             # Directly parse the RCS archive file.
835             #------------------------------------------------------------------
836             sub _parse_rcs_header {
837              
838 0     0     my $self = shift;
839 0           local $_;
840              
841 0           my ($head, %lock);
842 0           my (@access_list, @revisions);
843 0           my (%author, %date, %state, %symbols);
844              
845 0           my $rcsdir = $self->rcsdir;
846 0           my $file = $self->file;
847 0           my $rcs_file = $rcsdir . $Dir_Sep . $file . $self->arcext;
848              
849             # parse RCS archive file
850 0 0         open RCS_FILE, $rcs_file
851             or return(_rcsError "Unable to open $rcs_file: $!");
852 0           while () {
853 0 0         next if /^\s*$/; # skip blank lines
854 0 0         last if /^desc$/; # end of header info
855              
856             # get head revision
857 0 0         if (/^head\s/) {
858 0           ($head) = /^head\s+(.*?);$/;
859 0           next;
860             }
861              
862             # get access list
863 0 0         if (/^access$/) {
864 0           while () {
865 0           chomp;
866 0           s/\s//g; # remove all whitespace
867 0           push @access_list, (split(/;/))[0];
868 0 0         last if /;$/;
869             }
870 0           next;
871             }
872              
873             # get symbols
874 0 0         if (/^symbols$/) {
875 0           while () {
876 0           chomp;
877 0           s/\s//g; # remove all whitespace
878 0           my ($sym, $rev) = split(/:/);
879 0           $rev =~ s/;$//;
880 0           $symbols{$sym} = $rev;
881 0 0         last if /;$/;
882             }
883 0           next;
884             }
885              
886             # get locker
887 0 0         if (/^locks/) {
888              
889             # file not locked
890 0 0         if (/;$/) {
891 0           %lock = ();
892 0           next;
893             }
894              
895             # get user who has file locked
896 0           while() {
897 0           s/\s+//g; # remove all white space
898 0 0         next unless $_ ; # skip blank line (now empty string)
899 0 0         last if /^;/; # end of locks
900 0           my ($locker, $rev) = split(/:/);
901 0           $rev =~ s/;.*//;
902 0           $lock{$rev} = $locker;
903 0 0         last if /;$/; # end of locks
904             }
905 0           next;
906             }
907              
908             # get all revisions
909 0 0         if (/^\d+\.\d+/) {
910 0           chomp;
911 0           push @revisions, $_;
912              
913             # get author, state and date of each revision
914 0           my $next_line = ;
915 0           chop(my $author = (split(/\s+/, $next_line))[3]);
916 0           chop(my $state = (split(/\s+/, $next_line))[5]);
917 0           chop(my $date = (split(/\s+/, $next_line))[1]);
918              
919             # store date as date number
920 0           my ($year, $mon, $mday, $hour, $min, $sec) = split(/\./, $date);
921 0           $mon--; # convert to 0-11 range
922 0           my @date = ($sec,$min,$hour,$mday,$mon,$year);
923              
924             # store value in hash using revision as key
925 0           $author{$_} = $author;
926 0           $state{$_} = $state;
927 0           $date{$_} = timegm(@date);
928             }
929             }
930 0           close RCS_FILE;
931              
932 0           $self->{HEAD} = $head;
933 0           $self->{LOCK} = \%lock;
934 0           $self->{ACCESS} = \@access_list;
935 0           $self->{REVISIONS} = \@revisions;
936 0           $self->{AUTHOR} = \%author;
937 0           $self->{DATE} = \%date;
938 0           $self->{STATE} = \%state;
939 0           $self->{SYMBOLS} = \%symbols;
940             }
941              
942             #------------------------------------------------------------------
943             # _rcsError
944             #------------------------------------------------------------------
945             sub _rcsError {
946 0     0     my $error_msg = shift;
947              
948 0 0         not $nonFatal and croak $error_msg;
949 0 0 0       $nonFatal and not $Quiet and carp $error_msg and return 0;
      0        
950 0 0 0       $nonFatal and $Quiet and return 0;
951             }
952              
953             1;
954              
955             __END__