File Coverage

blib/lib/Cvs/Command/Base.pm
Criterion Covered Total %
statement 93 206 45.1
branch 15 96 15.6
condition 8 44 18.1
subroutine 22 34 64.7
pod 1 15 6.6
total 139 395 35.1


line stmt bran cond sub pod time code
1             package Cvs::Command::Base;
2              
3 4     4   32 use strict;
  4         10  
  4         187  
4 4     4   25 use Cwd;
  4         9  
  4         749  
5 4     4   7346 use IPC::Run;
  4         215202  
  4         228  
6 4     4   46 use base qw(Class::Accessor);
  4         13  
  4         8321  
7              
8             Cvs::Command::Base->mk_accessors
9             (qw(
10             cvsroot command args cvs need_workdir
11             initial_context result go_into_workdir
12             ));
13              
14             sub new
15             {
16 4     4 1 20 my($proto, $cvs, @args) = @_;
17 4   33     56 my $class = ref $proto || $proto;
18 4         11 my $self = {};
19 4         13 bless($self, $class);
20 4 50       33 $self->cvs($cvs) or
21             die "this shouldn't happend";
22 4         154 $self->go_into_workdir(1);
23 4         56 $self->need_workdir(1);
24 4         45 return ($self->init(@args))[0];
25             }
26              
27             sub init
28             {
29 4     4 0 20 return shift;
30             }
31              
32 9     9 0 107 sub workdir {shift->cvs->workdir()}
33              
34             sub run
35             {
36 4     4 0 9 my($self) = @_;
37 4         13 my $debug = $self->cvs->debug();
38              
39             #
40             # Preparing environment and parameters
41             #
42 4         81 my $old_pwd;
43 4 100       11 if($self->need_workdir())
44             {
45             # this can append when the Cvs object is created without a
46             # working directory, and a sub-command that need it is called
47 3 50       36 return $self->err_result('no such working directory')
48             unless defined $self->workdir();
49              
50             # keep current working directory
51 3         20846 $old_pwd = cwd();
52              
53 3 50       383 if($self->go_into_workdir())
54             {
55 0 0       0 print STDERR "** Chdir to: ", $self->cvs->working_directory(), "\n"
56             if $debug;
57 0         0 chdir($self->cvs->working_directory());
58             }
59             else
60             {
61 3 50       150 print STDERR "** Chdir to: ", $self->cvs->pwd(), "\n"
62             if $debug;
63 3         44 chdir($self->cvs->pwd());
64             }
65             }
66              
67             # getting sub-command
68 4         635 my $sub_command = $self->command();
69 4 50       80 unless(defined $sub_command)
70             {
71 0 0       0 if(defined $self->result())
72             {
73             # this command don't need to be ran
74 0         0 return $self->result();
75             }
76             else
77             {
78 0         0 return $self->err_result('empty result, it\'s a bug !')
79             }
80             }
81             # getting cvsroot
82 4   33     134 my $cvsroot = $self->cvsroot() || $self->cvs->cvsroot();
83 4 50       35 return $self->err_result('no such cvsroot')
84             unless(defined $cvsroot);
85              
86             # getting context, if none we spawn one
87 4         39 my $context = $self->initial_context();
88 4 50       117 unless(defined $context)
89             {
90 0         0 $context = $self->new_context();
91 0         0 $self->initial_context($context);
92             }
93              
94             # bind cvsroot handlers to the context
95 4         59 $cvsroot->bind($self);
96             # bind common handlers
97 4         163 $self->bind();
98              
99 4         364 my @command = ('cvs');
100 4         39 push @command, '-f', '-d', $cvsroot->cvsroot();
101 4 50       307 push @command, '-t' if $debug > 1;
102 4         60 push @command, $sub_command, $self->args();
103              
104             #
105             # Starting command
106             #
107 4 50       20 print(STDERR join(' ', '>>', @command), "\n")
108             if $debug;
109 4         23 my($in, $out) = ('', '');
110             # pty is needed for login sub-command (and maybe for something
111             # else) because it open pty for prompting the password :(
112 4         72 my $h = IPC::Run::harness(\@command, \$in, '>pty>', \$out, '2>&1');
113 4         57312 $h->start();
114 0         0 $self->{harness} = $h;
115              
116              
117             #
118             # Parsing command result
119             #
120             # It's not trivial to parse the cvs output, because the output may
121             # stall, and we never be sure that the command as finish, if a
122             # line is complete or if the command is waiting for input (like a
123             # password).
124 0         0 my($first, $last, $line, $match, $debugline);
125 0   0     0 while(defined $context && $h->pump && length $out)
      0        
126             {
127 0         0 $first = 1;
128 0         0 $match = 0;
129              
130             # flushing the send buffer
131 0         0 $self->{data} = '';
132              
133 0 0       0 print STDERR "** new chunk\n"
134             if $debug;
135              
136 0   0     0 while(defined $context && $out =~ /.*?(?:\r?\n|$)/g)
137             {
138             # my unperfect regexp match an empty line at end of
139             # certain strings... skip it
140 0 0       0 next unless length $&;
141              
142 0         0 $line = $&;
143 0         0 $line =~ s/\r/\n/g;
144 0         0 $line =~ s/\n+/\n/g;
145 0 0       0 if($debug)
146             {
147 0 0 0     0 if($line =~ /^(?: |S)-> / or
      0        
148             (defined $debugline and $debugline eq 'unterminated'))
149             {
150 0         0 print STDERR $line;
151             # if the cvs debug line is truncated, try to not
152             # treat next parts as real cvs response component
153 0 0       0 $debugline = $line =~ /\n/ ? undef : 'unterminated';
154 0         0 undef $line;
155 0         0 next;
156             }
157 0         0 $debugline = $line;
158             # make CR and LF visible
159 0         0 $debugline =~ s/\r/\\r/g;
160 0         0 $debugline =~ s/\n/\\n/g;
161 0         0 print STDERR "<< $debugline\n";
162             }
163              
164             # don't analyse empty lines, but $line have to be set
165 0 0       0 next if $line =~ /^\n*$/;
166              
167             # Analysing the line: if a context is return, we replace
168             # the current one with it to handling context
169             # switching. If an undef value is returned, it's means
170             # that no further analyse will be expected. The second
171             # element is a boolean value which be true if the line
172             # matched.
173 0         0 ($context, $match) = $context->analyse($line);
174              
175             # this variable isn't relevant for others than first line,
176             # see comments below
177 0 0       0 undef($last) unless $first;
178              
179 0 0       0 if(not $match)
180             {
181 0 0       0 if(defined $last)
182             {
183             # cvs sends its output in chunks and each chunk
184             # doesn't necessary finish at the end of the
185             # line. So we recover the last line of last chunk
186             # if it was unmatched by any rules and we join it
187             # with the first line of the current chunk if it
188             # wasn't match too, to see if it match more.
189 0         0 ($context, $match) = $context->analyse("$last$line");
190 0 0       0 if($debug)
191             {
192 0 0       0 my $un = $match ? '' : 'un';
193 0         0 print STDERR
194             "** ${un}matched recomposed line: $last$debugline\n";
195             }
196             }
197             else
198             {
199 0 0       0 print STDERR "** unmatched line: $debugline\n"
200             if $debug;
201             }
202             }
203              
204 0         0 $first = 0;
205             }
206             # we don't want to parse several times the same thing
207 0         0 $out = '';
208              
209             # keep the last line if it doesn't be used, it's maybe an
210             # unterminated line. If line end with line-feed, this can't be
211             # an unterminated line.
212 0 0 0     0 if($match or not defined $line or $line =~ /\n$/)
    0 0        
213             {
214 0         0 undef($last);
215             }
216             elsif(length $line)
217             {
218 0         0 $last .= $line;
219 0 0       0 print STDERR "** new \$last value: $last\n"
220             if $debug;
221             }
222              
223             # check out if some input want be send
224 0 0       0 if(length $self->{data})
225             {
226 0         0 $in = $self->{data};
227 0 0       0 print STDERR ">> $in\n"
228             if $debug;
229             # wait for all input to go
230 0         0 $h->pump_nb while length $in;
231             }
232             }
233              
234 0   0     0 my $rv = $h->finish() || $?;
235              
236             #
237             # Restoring/cleaning-up environment
238             #
239             # exec cleanup codes if any
240 0 0       0 if(defined $self->{cleanup})
241             {
242 0 0       0 print STDERR "** Do some cleanup tasks\n"
243             if $debug;
244 0         0 &$_ for @{$self->{cleanup}};
  0         0  
245             }
246             # back to the old working directory if needed
247 0 0       0 chdir($old_pwd) if $self->need_workdir();
248              
249             #
250             # Returning the result
251             #
252 0         0 my $result = $self->result;
253             # should not happened
254 0 0       0 return $self->err_result('empty result, it\'s a bug !')
255             unless defined $result;
256 0 0       0 $result->success($rv)
257             unless defined $result->success();
258 0         0 return $result;
259             }
260              
261             sub err_result
262             {
263 0     0 0 0 my($self, $msg) = @_;
264              
265 0         0 my $result = $self->result();
266 0 0       0 unless(defined $result)
267             {
268 0         0 $result = new Cvs::Result::Base;
269 0         0 $self->result($result);
270             }
271              
272 0         0 $result->success(0);
273 0         0 $result->error($msg);
274 0         0 return $result;
275             }
276              
277             sub push_cleanup
278             {
279 0     0 0 0 my($self, $code) = @_;
280 0         0 push @{$self->{cleanup}}, $code;
  0         0  
281             }
282              
283             sub restart
284             {
285 0     0 0 0 my($self) = @_;
286             # restart command
287 0         0 $self->{harness}->finish();
288 0         0 $self->{harness}->start();
289             }
290              
291             sub send
292             {
293 0     0 0 0 my($self, $data) = @_;
294 0 0       0 if(defined $data)
295             {
296 0         0 $self->{data} .= $data;
297             }
298             }
299              
300             sub bind
301             {
302 4     4 0 16 my($self) = @_;
303              
304 4         24 my $context = $self->initial_context();
305             $context->push_handler
306             (
307             qr/^cvs \[.* aborted\]: (.+)$/, sub
308             {
309 0     0   0 $self->err_result(shift->[1]);
310 0         0 return $context->finish();
311             }
312 4         298 );
313             }
314              
315             sub default_params
316             {
317 4     4 0 27 my($self, %param) = @_;
318              
319 4         16 foreach(keys %param)
320             {
321 18   66     90 $self->{param}->{$_} ||= $param{$_};
322             }
323             }
324              
325             sub param
326             {
327 20     20 0 30 my($self, $param) = @_;
328              
329 20 100 66     61 if(defined $param && ref $param eq 'HASH')
330             {
331 2         8 foreach(keys %$param)
332             {
333 1 50       7 $self->{param}->{$_} = $param->{$_}
334             if exists $param->{$_};
335             }
336             }
337 20   50     121 return $self->{param} || {};
338             }
339              
340             sub push_arg
341             {
342 8     8 0 56 my($self, @args) = @_;
343 8         11 push @{$self->{args}}, @args;
  8         28  
344             }
345              
346             sub args
347             {
348 4     4 0 17 my($self) = @_;
349 4 50       46 return @{$self->{args}||[]};
  4         119  
350             }
351              
352             sub new_context
353             {
354 4     4 0 8 my($self) = @_;
355 4         19 return Cvs::Command::Context->new();
356             }
357              
358             sub error
359             {
360 0     0 0 0 my($self, @msg) = @_;
361 0   0     0 my $package = ref $self || $self;
362 4     4   33 no strict 'refs';
  4         10  
  4         392  
363 0 0       0 if(@msg)
364             {
365 0         0 ${$package."::ERROR"} = join(' ', @msg);
  0         0  
366 0         0 return undef;
367             }
368             else
369             {
370 0         0 return ${$package."::ERROR"};
  0         0  
371             }
372             }
373              
374             package Cvs::Command::Context;
375              
376 4     4   22 use strict;
  4         8  
  4         137  
377 4     4   26 use constant LAST => -1;
  4         7  
  4         451  
378 4     4   26 use constant FINISH => -2;
  4         7  
  4         207  
379 4     4   24 use constant CONTINUE => -3;
  4         12  
  4         193  
380 4     4   19 use constant RESCAN => -4;
  4         8  
  4         2030  
381              
382             sub new
383             {
384 4     4   49 my($proto) = @_;
385 4   33     24 my $class = ref $proto || $proto;
386 4         14 my $self = {};
387 4         57 $self->{rules} = [];
388 4         15 bless($self, $class);
389 4         12 return $self;
390             }
391              
392 0     0   0 sub last {return -1}
393 0     0   0 sub finish {return -2}
394 0     0   0 sub continue {return -3}
395 0     0   0 sub catched {return shift->{catched}}
396              
397             sub rescan_with
398             {
399 0     0   0 my($self, $context) = @_;
400              
401 0 0       0 if(defined $context)
402             {
403 0         0 $self->{rescan_context} = $context;
404 0         0 return RESCAN;
405             }
406 0         0 return $self->{rescan_context};
407             }
408              
409             sub push_handler
410             {
411 8     8   64 my($self, $pattern, $code, @args) = @_;
412 8         12 push @{$self->{rules}}, [$pattern, $code, @args];
  8         257  
413             }
414              
415             sub analyse
416             {
417 0     0     my($self, $line) = @_;
418              
419 0           my $match = 0;
420 0           foreach (@{$self->{rules}})
  0            
421             {
422 0           my($pattern, $code, @args) = @$_;
423 0 0         if(my @match = $line =~ /$pattern/)
424             {
425 0           $match++;
426 0           my $rv = &$code([$line, @match], @args);
427 0 0         if(defined $rv)
428             {
429 0 0         if(ref $rv eq 'Cvs::Command::Context')
    0          
    0          
    0          
430             {
431             # switching to another area
432 0           return($rv, $match);
433             }
434             elsif($rv eq $self->continue)
435             {
436 0           next;
437             }
438             elsif($rv eq $self->finish)
439             {
440 0           return(undef, $match);
441             }
442             elsif($rv eq RESCAN)
443             {
444 0           my $context = $self->rescan_with();
445 0 0         if(defined $context)
446             {
447 0           return $context->analyse($line);
448             }
449             }
450             }
451             # if last (default behavior)
452 0           return($self, $match);
453             }
454             }
455              
456 0           return($self, $match);
457             }
458              
459             1;
460             =pod
461              
462             =head1 LICENCE
463              
464             This library is free software; you can redistribute it and/or modify
465             it under the terms of the GNU Lesser General Public License as
466             published by the Free Software Foundation; either version 2.1 of the
467             License, or (at your option) any later version.
468              
469             This library is distributed in the hope that it will be useful, but
470             WITHOUT ANY WARRANTY; without even the implied warranty of
471             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
472             Lesser General Public License for more details.
473              
474             You should have received a copy of the GNU Lesser General Public
475             License along with this library; if not, write to the Free Software
476             Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
477             USA
478              
479             =head1 COPYRIGHT
480              
481             Copyright (C) 2003 - Olivier Poitrey
482