File Coverage

blib/lib/Cvs/Simple.pm
Criterion Covered Total %
statement 110 205 53.6
branch 32 80 40.0
condition 10 24 41.6
subroutine 29 42 69.0
pod 20 20 100.0
total 201 371 54.1


line stmt bran cond sub pod time code
1             package Cvs::Simple;
2 9     9   969808 use common::sense;
  9         84  
  9         49  
3              
4 9     9   10229 use Capture::Tiny qw(capture_merged);
  9         275101  
  9         669  
5 9     9   80 use Carp;
  9         19  
  9         535  
6 9     9   9488 use Class::Std::Utils;
  9         36306  
  9         57  
7 9     9   7260 use Cvs::Simple::Hook;
  9         26  
  9         296  
8              
9 9     9   6387 use File::Which qw(which);
  9         7077  
  9         551  
10              
11 9     9   8551 use IO::Lines;
  9         57821  
  9         681  
12              
13 9     9   8933 use Module::Runtime qw(require_module);
  9         16615  
  9         57  
14              
15 9     9   661 use Scalar::Util qw(reftype);
  9         16  
  9         528  
16              
17 9     9   9279 use Try::Tiny;
  9         15523  
  9         40528  
18              
19             # Version set by dist.ini; do not change here.
20             our $VERSION = '0.07_04'; # VERSION
21              
22             {
23             my(%cvs_bin_of);
24             my(%external_of);
25             my(%callback_of);
26             my(%repos_of);
27              
28             sub new
29             {
30 9     9 1 9758 my $class = shift;
31 9         68 my $self = bless(anon_scalar(), $class);
32 9         133 $self->_init(@_);
33 9         31 return $self;
34             }
35              
36             sub _init
37             {
38 9     9   27 my $self = shift;
39 9         43 my %args = @_;
40              
41 9 100       47 if(exists $args{cvs_bin}) {
42 7         39 $self->cvs_bin($args{cvs_bin});
43             } else {
44 2 100       10 if(defined($ENV{CVS_SIMPLE_BIN})) {
45 1         5 $self->cvs_bin($ENV{CVS_SIMPLE_BIN});
46             } else {
47             my $m =
48 1     1   48 try { require_module('Cvs::Simple::Config') }
49 1     0   9 catch { undef };
  0         0  
50 1 50       44 if(defined($m)) {
    0          
51             my $cvs_bin =
52 1     1   30 try { Cvs::Simple::Config::CVS_BIN() }
53 1     0   9 catch { warn($_); undef; };
  0         0  
  0         0  
54 1 50       16 if(defined($cvs_bin)) {
55 1         4 $self->cvs_bin( $cvs_bin );
56             } else {
57 0         0 croak("csv binary location not specified and cannot find.");
58             }
59              
60             my $ext =
61 1     1   25 try { Cvs::Simple::Config::EXTERNAL() }
62 1     0   9 catch { undef; };
  0         0  
63 1 50       14 if(defined($ext)) {
64 1         4 $self->external( $ext );
65             }
66             } elsif ( my $exe_path = which('cvs') ) {
67 0         0 $self->cvs_bin( $exe_path );
68             } else {
69 0         0 croak("csv binary location not specified and cannot find.");
70             }
71             }
72             }
73              
74 9 50       58 if(exists($args{external})) {
    50          
75 0         0 $self->external($args{external});
76             } elsif (defined($ENV{CVS_SIMPLE_EXTERNAL})) {
77 0         0 $self->external($ENV{CVS_SIMPLE_EXTERNAL});
78             }
79             else {
80 9         37 ();
81             }
82              
83 9 50       49 if(exists $args{callback}) {
84 0         0 $self->callback($args{callback});
85             }
86             }
87              
88             sub callback
89             {
90 4     4 1 1946 my $self = shift;
91 4         83 my $hook = shift;
92 4         8 my $func = shift;
93              
94             # If 'hook' is not supplied, callback is global, i.e. apply to all.
95 4 50       17 defined($hook) || ($hook = 'All');
96              
97 4 50       18 unless(Cvs::Simple::Hook::permitted($hook)) {
98 0         0 croak "Invalid hook type in callback: $hook.";
99             }
100              
101 4 100       12 if(defined($func)) {
102 3 50       16 (reftype($func) eq 'CODE') or do {
103 0         0 croak "Argument supplied to callback() should be a coderef.";
104             };
105 3         18 $callback_of{ident $self}{$hook} = $func;
106             }
107              
108 4 50       16 if(exists $callback_of{ident $self}{$hook}) {
109 4         23 return $callback_of{ident $self}{$hook};
110             } else {
111 0         0 return;
112             }
113             }
114              
115             sub unset_callback
116             {
117 0     0 1 0 my $self = shift;
118 0         0 my $hook = shift;
119              
120 0 0       0 unless(Cvs::Simple::Hook::permitted($hook)) {
121 0         0 croak "Invalid hook type in unset_callback: $hook.";
122             }
123              
124 0         0 return delete $callback_of{ident $self}{$hook};
125             }
126              
127             sub cvs_bin
128             {
129 16     16 1 424 my $self = shift;
130              
131 16 100       73 if(@_==1) {
132 9         64 $cvs_bin_of{ident $self} = shift;
133             }
134              
135 16         74 return $cvs_bin_of{ident $self};
136             }
137              
138             sub _pipe
139             {
140 0     0   0 my $self = shift;
141 0         0 my $cmd = shift;
142              
143             my ($output,@result) = capture_merged {
144 0     0   0 system( $cmd );
145 0         0 };
146              
147             # 'man cvs' tells us that the return code from 'cvs diff' never
148             # indicates an error, so we only check this for other commands.
149 0 0       0 if($cmd =~ m{\bdiff\b}) {
150 0 0       0 if($output =~ m{unknown command}i) {
151 0         0 croak("Failed to execute diff command: $output");
152             } else {
153             ()
154 0         0 }
155             } else {
156 0 0       0 if($result[0] == 0 ) {
157 0         0 ();
158             } else {
159 0         0 croak("Failed to execute command: $output");
160             }
161             }
162              
163 0         0 my $SH = IO::Lines->new();
164 0         0 $SH->print( $output );
165 0         0 return $SH;
166             }
167              
168             sub cvs_cmd
169             {
170 0     0 1 0 my $self = shift;
171 0         0 my $cmd = shift;
172              
173 0 0 0     0 croak "Syntax: cvs_cmd(cmd)" unless (defined($cmd) && $cmd);
174              
175 0         0 STDOUT->autoflush;
176              
177 0         0 my $hook = Cvs::Simple::Hook::get_hook $cmd;
178              
179 0         0 my $fh = $self->_pipe( $cmd );
180              
181 0   0     0 my($hookfunc) = $self->callback($hook) ||
182             $self->callback();
183              
184 0 0       0 if(defined( $hookfunc )) {
185 0         0 while(defined($_=$fh->getline)) {
186 0         0 $hookfunc->( $cmd, $_ );
187             }
188             } else {
189 0         0 print STDOUT $fh->getlines;
190             }
191              
192 0         0 $fh->close;
193              
194 0         0 return 1;
195             }
196              
197             sub merge
198             {
199             # merge(old_rev,new_rev,file);
200 2     2 1 1087 my $self = shift;
201 2         22 my @args = @_;
202              
203 2 50 66     46 croak "Syntax: merge(old_rev,new_rev,file)"
204             unless (@args && scalar(@args)==3);
205              
206 0         0 my $cmd = $self->_cmd('-q update');
207 0         0 $cmd .= sprintf("-j%s -j%s %s", @args);
208              
209 0         0 return $self->cvs_cmd($cmd);
210             }
211              
212             sub undo
213             {
214 0     0 1 0 goto &backout;
215             }
216              
217             sub backout
218             {
219             # Revert to previous revision of a file, i.e. backout/undo change(s).
220             # backout(current_rev,revert_rev,file);
221 2     2 1 1181 my $self = shift;
222 2         26 my @args = @_;
223              
224 2 50 66     13 unless (@args && scalar(@args)==3) {
225 2         37 croak <
226             Syntax: backout(current_rev,revert_rev,file)
227             undo (current_rev,revert_rev,file)
228             SYN
229             }
230              
231 0         0 return $self->merge(@args);
232             }
233              
234             sub external
235             {
236 6     6 1 11 my $self = shift;
237              
238 6 100       21 if(@_==1) {
239 1         5 $repos_of{ident $self} = shift;
240             }
241 6         46 return $repos_of{ident $self};
242             }
243              
244             sub _cmd
245             {
246 4     4   5 my $self = shift;
247 4         6 my $type = shift;
248              
249 4         12 my $cvs = $self->cvs_bin;
250              
251 4 50       15 my $cmd =
252             $self->external
253             ? sprintf("%s -d %s %s ", $cvs,$self->external,$type)
254             : sprintf("%s %s ", $cvs,$type);
255              
256 4         11 return $cmd;
257             }
258              
259             sub add
260             {
261             # Can only be called as:
262             # cvs add file1 [, .... , ]
263 2     2 1 977 my $self = shift;
264 2         4 my @args = @_;
265              
266 2 50       65 croak "Syntax: add(file1, ...)" unless(@args);
267              
268 0         0 my $cmd = $self->_cmd('add');
269              
270 0 0       0 if(@args) {
271 0         0 $cmd .= join ' ' => @args;
272             }
273              
274 0         0 return $self->cvs_cmd($cmd);
275             }
276              
277             sub add_bin
278             {
279             # Can only be called as :
280             # cvs add -kb file1 [, .... , ]
281 2     2 1 1593 my($self) = shift;
282 2         6 my(@args) = @_;
283              
284 2 50       35 croak "Syntax: add_bin(file1, ...)" unless (@args);
285              
286 0         0 my($cmd) = $self->_cmd('add -kb');
287              
288 0 0       0 if(@args) {
289 0         0 $cmd .= join ' ' => @args;
290             }
291              
292 0         0 return $self->cvs_cmd($cmd);
293             }
294              
295             sub checkout
296             {
297             # Can be called as:
298             # cvs co module
299             # cvs co -r tag module
300             # Calling signature is checkout(tag,module) or checkout(module).
301 4     4 1 2376 my($self) = shift;
302 4         30 my(@args) = @_;
303              
304 4 50 33     33 unless (@args && (scalar(@args)==2 || scalar(@args)==1)) {
      66        
305 4         64 croak <
306             Syntax: co(tag)
307             co(module)
308             checkout(tag)
309             checkout(module)
310             SYN
311              
312             }
313              
314 0         0 my($cmd) = $self->_cmd('co');
315              
316 0 0       0 $cmd .= @args==2 ? sprintf("-r %s %s", @args)
317             : sprintf("%s", @args);
318              
319 0         0 return $self->cvs_cmd($cmd);
320             }
321              
322             sub co {
323 2     2 1 2542 goto &checkout;
324             }
325              
326             sub _pattern {
327 0     0   0 my $self = shift;
328 0         0 my @args = @_;
329 0         0 return join '' => ('%s ' x @{$args[0]});
  0         0  
330             }
331              
332             sub commit {
333             # Can be called as :
334             # commit()
335             # commit([file_list])
336             # commit(tag1)
337             # commit(tag1, [file_list])
338 4     4 1 2070 my $self = shift;
339 4         35 my @args = @_;
340              
341 4         14 my $cmd = $self->_cmd('commit -m ""');
342 4 50       17 if(@args==0) { # 'cvs commit -m ""'
    100          
    50          
343 0         0 return $self->cvs_cmd($cmd);
344             } elsif(@args==2) { # 'cvs commit -m "" -r TAG file(s)'
345 2 50       31 croak "Syntax: commit([rev],[\@filelist])"
346             unless (reftype($args[1]) eq 'ARRAY');
347 0         0 my $pattern = join '' => '-r %s ', $self->_pattern($args[1]);
348 0         0 $cmd .= sprintf($pattern, $args[0], @{$args[1]});
  0         0  
349 0         0 return $self->cvs_cmd($cmd);
350             } elsif(@args==1) { # 'cvs commit -m "" -r TAG' or
351             # 'cvs commit -m "" file(s)'
352 0         0 my($pattern);
353 0 0       0 if(reftype($args[0]) eq 'ARRAY') {
354 0         0 $pattern = sprintf($self->_pattern($args[0]), @{$args[0]});
  0         0  
355             }
356             else {
357 0         0 $pattern = sprintf('-r %s', $args[0]);
358             }
359              
360 0         0 $cmd .= $pattern;
361              
362 0         0 return $self->cvs_cmd($cmd);
363             } else { # Anything else is an error
364 2         45 croak <
365             Syntax: commit([rev],[\@filelist])
366             ci ([rev],[\@filelist])
367             SYN
368              
369             }
370             }
371              
372             sub ci
373             {
374 2     2 1 1753 goto &commit;
375             }
376              
377             sub diff
378             {
379             # Can be called as :
380             # diff(file_or_dir)
381             # diff(tag1,tag2,file_or_dir)
382 2     2 1 1908 my($self) = shift;
383 2         58 my(@args) = @_;
384              
385 2 50 33     60 croak "Syntax: diff(file) or diff(tag1,tag2,file)"
      66        
386             unless (@args && (scalar(@args)==1 || scalar(@args)==3));
387              
388 0         0 my($cmd) = $self->_cmd('diff -c');
389              
390 0 0       0 $cmd .= @args==3
391             ? sprintf("-r %s -r %s %s", @args)
392             : sprintf("%s" , @args);
393              
394 0         0 return $self->cvs_cmd($cmd);
395             }
396              
397             sub status
398             {
399             # status()
400             # status(file1, ... )
401 0     0 1 0 my($self) = shift;
402 0         0 my(@args) = @_;
403              
404 0         0 my($cmd) = $self->_cmd('status -v');
405              
406 0 0       0 if(@args) {
407 0         0 $cmd .= join ' ' => @args;
408             }
409              
410 0         0 return $self->cvs_cmd($cmd);
411             }
412              
413             sub upd
414             {
415 0     0 1 0 goto &update;
416             }
417              
418             sub update
419             {
420             # update() -> update workspace (cvs -q update -d).
421             # update(file) -> update file (cvs -q update file [file ... ]).
422             # Doesn't permit -r.
423 0     0 1 0 my($self) = shift;
424 0         0 my(@args) = @_;
425              
426 0         0 my($cmd) = $self->_cmd('-q update');
427              
428 0 0       0 $cmd .= @args ? join ' ' => @args
429             : '-d';
430              
431 0         0 return $self->cvs_cmd($cmd);
432             }
433              
434             sub up2date
435             {
436             # Checks workspace status. No args.
437 0     0 1 0 my($self) = shift;
438              
439 0         0 my($cmd) = $self->_cmd('-nq update -d');
440              
441 0         0 return $self->cvs_cmd($cmd);
442             }
443              
444             sub DESTROY
445             {
446 9     9   7581 my($self) = shift;
447 9         56 delete($cvs_bin_of {ident $self});
448 9         47 delete($external_of{ident $self});
449 9         36 delete($callback_of{ident $self});
450              
451 9         790 return;
452             }
453             }
454              
455             1;
456              
457             # ABSTRACT: Perl interface to cvs.
458              
459             __END__