File Coverage

blib/lib/Vi/QuickFix.pm
Criterion Covered Total %
statement 85 174 48.8
branch 21 76 27.6
condition 12 34 35.2
subroutine 24 38 63.1
pod 0 12 0.0
total 142 334 42.5


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             package Vi::QuickFix;
3 1     1   2340920 use 5.008_000;
  1         3  
4 1     1   9 use strict; use warnings;
  1     1   11  
  1         34  
  1         5  
  1         1  
  1         67  
5             # use Carp;
6              
7             our $VERSION;
8             BEGIN {
9 1     1   131 $VERSION = ('$Revision: 1.135 $' =~ /(\d+.\d+)/)[ 0];
10             }
11              
12             unless ( caller ) {
13             # process <> if called as an executable
14             exec_mode(1); # signal fact ( to END processing)
15             require Getopt::Std;
16             Getopt::Std::getopts( 'q:f:v', \ my %opt);
17             print "$0 version $VERSION\n" and exit 0 if $opt{ v};
18             err_open( $opt{ q} || $opt{ f});
19             print && err_out( $_) while <>;
20             exit;
21             }
22              
23             ###########################################################################
24              
25             # keywords for ->import
26 1     1   10 use constant KEYWORDS => qw(silent sig tie fork);
  1         2  
  1         84  
27              
28             # environment variable(s)
29 1     1   5 use constant VAR_SOURCEFILE => 'VI_QUICKFIX_SOURCEFILE';
  1         2  
  1         935  
30              
31             BEGIN {{ # space for private variables
32              
33 1         2 my $relay = ''; # method of transfer to error file: "sig" or "tie"
  0         0  
34 1         3 my %invocation; # from where was import() called?
35              
36             sub import {
37 4     4   3177 my $class = shift;
38 4         11 my %keywords;
39 4         18 @keywords{ KEYWORDS()} = ();
40 4   100     44 $keywords{ shift()} = 1 while @_ and exists $keywords{ $_[ 0]};
41              
42 4         10 my $filename = shift;
43 4 100       17 make_silent() if $keywords{ silent};
44 4         20 my ( $wanted_relay) = grep $keywords{ $_}, qw( sig tie fork);
45 4   33     14 $relay = $wanted_relay || default_relay();
46 4 100       13 if ( my $reason = relay_obstacle( $relay) ) {
47 1         231 croak( "Cannot use '$relay' method: $reason");
48             }
49 3 50       17 err_open($filename) unless $relay eq 'fork'; # happens in background
50 2 50       129 if ( $relay eq 'tie' ) {
    0          
    0          
51             # if tied, it's tied to ourselves (otherwise obstacle)
52 2 100       18 tie *STDERR, 'Vi::QuickFix::Tee', '>&STDERR' unless tied *STDERR;
53             } elsif ( $relay eq 'sig' ) {
54 0         0 $SIG{ $_} = Vi::QuickFix::SigHandler->new( $_) for
55             qw( __WARN__ __DIE__);
56             } elsif ( $relay eq 'fork' ) {
57 0         0 *STDERR = fork_relay($filename);
58             }
59             # save invocation for obligate message
60 2         74 (undef, @invocation{qw(file line)}) = caller;
61             }
62              
63             # internal variables
64             {
65 1         1 my $exec_mode; # set if lib file is run as a script
  0         0  
66             sub exec_mode {
67 0 0   0 0 0 $exec_mode = shift if @_;
68 0         0 $exec_mode;
69             }
70            
71 1         1 my $silent = 0; # switch off otherwise obligatory warning
72 2     2 0 4 sub make_silent { $silent = 1 }
73 2     2 0 13 sub is_silent { $silent }
74              
75 1         2 my $errfile = 'errors.err'; # name of error file
76 1         1 my $errhandle; # write formatted errors here
77             # open the given file (or default), set $errfile and $errhandle
78             sub err_open {
79 3   100 3 0 15 $errfile = shift || 'errors.err';
80 3 100       20 $errhandle = IO::File->new( $errfile, '>') or warn(
81             "Can't create error file '$errfile': $!"
82             );
83 2 50       323 $errhandle->autoflush if $errhandle;
84             }
85              
86             sub err_print {
87 0 0   0 0 0 print $errhandle @_ if $errhandle;
88             }
89              
90             sub err_clean {
91 1     1 0 3 my $unlink = shift;
92 1 50       8 close $errhandle if $errhandle;
93 1 50 33     81 unlink $errfile if $errfile and $unlink and not -s $errfile;
      33        
94             }
95             }
96              
97 1         2 sub err_out {
98             # handle multiple, possibly multi-line messages (though usually
99             # there will be only one)
100 0     0 0 0 for ( map split( /\n+/), @_ ) {
101 0         0 my $out;
102 0 0       0 if ( /.+:\d+:/ ) { # already in QuickFix format, pass on
103 0         0 err_print("$_\n");
104             } else {
105 0         0 for ( parse_perl_msg($_) ) {
106 0 0       0 my ( $message, $file, $line, $rest) = @$_ or next;
107 0 0       0 $message .= $rest if $rest =~ s/^,//;
108 0   0     0 $file eq '-' and defined and $file = $_ for
      0        
109             $ENV{ VAR_SOURCEFILE()};
110 0         0 err_print("$file:$line:$message\n");
111             }
112             }
113             }
114             }
115              
116             # use constant PERL_MSG => qr/^(.*?) at (.*?) line (\d+)(\.?|,.*)$/;
117             sub parse_perl_msg {
118 0     0 0 0 my @coll;
119 0         0 for ( shift ) {
120 0         0 while ( m/ at /g ) {
121 0         0 my $text = substr($_, 0, $-[0]);
122 0         0 my $pos = pos;
123 0         0 while ( m/ line (\d+)(\.?|,.*)$/g ) {
124 0         0 my $file = substr($_, $pos, $-[0] - $pos);
125 0         0 my $line = $1;
126 0         0 my $rest = $2;
127 0         0 push @coll, [$text, $file, $line, $rest];
128             }
129 0         0 pos = $pos;
130             }
131             }
132 0 0       0 return @coll if @coll <= 1;
133 0         0 my @existing = grep -e $_->[1], @coll;
134 0 0       0 return @existing if @existing;
135 0         0 return @coll;
136             }
137              
138             # issue warning, erase error file
139 1         28 my $end_entiteled = $$;
140             END {
141             # issue warning (only original process, and not in exec mode)
142 1 0 33 1   600 unless ( is_silent or exec_mode() or $$ != $end_entiteled ) {
      33        
143 0         0 my $invocation_at;
144 0 0       0 if ( %invocation ) {
145 0         0 $invocation_at = "at $invocation{file} line $invocation{line}";
146             } else {
147 0         0 $invocation_at = "at -M";
148             }
149 0         0 warn "QuickFix ($relay) active $invocation_at\n";
150             }
151             # silently remove objects
152 1         5 make_silent();
153 1 50       3 if ( $relay eq 'tie' ) {
    0          
    0          
154 1         3 untie *STDERR;
155             } elsif ( $relay eq 'sig' ) {
156 0         0 $SIG{ $_} = 'DEFAULT' for qw( __WARN__ __DIE__);
157             } elsif ( $relay eq 'fork' ) {
158 0         0 close STDERR;
159 0         0 wait_kid();
160             }
161             # remove file if created by us and empty
162 1         4 err_clean($$ == $end_entiteled);
163             }
164              
165 1     1   4 }}
166              
167 1     1   5 use constant MINVERS => 5.008001; # minimum perl version for tie method
  1         1  
  1         149  
168             sub relay_obstacle {
169 4   50 4 0 12 my $relay = shift || '';
170 4 50       13 return '' unless $relay eq 'tie';
171 4 50       13 if ( $] < MINVERS ) {
172 0         0 return "perl version is $], must be >= @{[ MINVERS]}";
  0         0  
173             }
174 4 100       14 if ( my $tie_ob = tied *STDERR ) {
175 2         7 my $tieclass = ref $tie_ob;
176 2 100       29 return "STDERR already tied to '$tieclass'" unless
177             $tieclass eq 'Vi::QuickFix::Tee';
178             }
179 3         14 return '';
180             }
181              
182 0 0   0 0 0 sub default_relay { relay_obstacle( 'tie') ? 'sig' : 'tie' }
183              
184             {
185 1     1   6 use Carp;
  1         1  
  1         175  
186             my ($read, $write, $kid);
187             sub fork_relay {
188 0     0 0 0 my $filename = shift;
189 0         0 my $parent = $$;
190 0         0 pipe $read, $write;
191 0 0       0 if ( $kid = fork ) {
192             # parent
193 0         0 close $read;
194 0         0 return $write;
195             } else {
196 0 0       0 Carp::croak "Can't fork: $!" unless defined $kid;
197             # kid
198 0         0 close $write;
199 0         0 err_open($filename);
200 0         0 while ( <$read> ) {
201 0         0 print STDERR $_;
202 0         0 err_out($_);
203             }
204 0         0 err_clean(1);
205 0         0 exit;
206             }
207             }
208              
209 1     1   287 use POSIX ":sys_wait_h";
  1         4642  
  1         4  
210             sub wait_kid {
211 0     0 0 0 my $x;
212 0         0 do { $x = waitpid -1, WNOHANG } while $x > 0;
  0         0  
213             }
214             }
215              
216             # common destructor method
217             package Vi::QuickFix::Destructor;
218              
219 1     1   1263 use Carp qw( shortmess);
  1         2  
  1         46  
220 1     1   98 BEGIN { our @CARP_NOT = qw( Vi::QuickFix) }
221             sub DESTROY {
222 1     1   864 my $ob = shift;
223 1 50 33     4 return if Vi::QuickFix::is_silent or $^C; # it's a mess under -c
224 0           my $id = $ob->id;
225 0           my $msg = shortmess( "QuickFix $id processing interrupted");
226             # simulate intact QuickFix processing
227 0           Vi::QuickFix::err_out( $msg);
228 0           warn "$msg";
229             }
230              
231             # Class to associate a DESTROY method with sig handlers
232             package Vi::QuickFix::SigHandler;
233 1     1   6 use base qw( Vi::QuickFix::Destructor);
  1         1  
  1         413  
234              
235             # return a chaining handler for __WARN__ or __DIE__
236             sub new {
237 0     0     my $class = shift;
238 0           my $sig = shift;
239 0           my $prev_handler = $SIG{ $sig};
240             my $sub = sub {
241 0 0   0     return $sig unless @_; # backdoor
242 0 0 0       Vi::QuickFix::err_out( @_) unless $sig eq '__DIE__' and _in_eval();
243 0           my $code;
244             # resolve string at call time
245 0 0         if ( $prev_handler ) {
246             $code = ref $prev_handler ?
247             $prev_handler :
248 0 0         \ &{ 'main::' . $prev_handler};
  0            
249             }
250 0 0         goto &$code if $code;
251 0 0         die @_ if $sig eq '__DIE__';
252 0           warn @_;
253 0           };
254 0           bless $sub, $class; # so we can have a destructor
255             }
256              
257             sub _in_eval {
258 0     0     my $i = -1; # first call with 0
259 0           while ( defined(my $sub = (caller ++ $i)[3]) ) {
260 0 0         return 1 if $sub =~ /^\(eval/;
261             }
262 0           return 0;
263             }
264              
265             sub id {
266 0     0     my $handler = shift;
267 0           $handler->(); # call without args returns __WARN__ or __DIE__
268             }
269              
270             # tie class to tee re-formatted output to an error file
271             package Vi::QuickFix::Tee;
272              
273 1     1   252 use IO::File;
  1         10006  
  1         149  
274 1     1   364 use Tie::Handle;
  1         1338  
  1         24  
275 1     1   6 use base qw( Tie::StdHandle Vi::QuickFix::Destructor);
  1         2  
  1         280  
276              
277             sub WRITE {
278 0     0     my $fh = shift;
279 0           my ( $scalar, $length) = @_;
280 0           Vi::QuickFix::err_out( $scalar);
281 0           $fh->Tie::StdHandle::WRITE( @_);
282             }
283              
284             # work around buggy BINMODE in Tie::Stdhandle
285              
286             sub BINMODE {
287 0     0     binmode($_[0], $_[1])
288             }
289              
290 0     0     sub id { 'STDERR' }
291              
292             1;
293              
294             __END__