File Coverage

blib/lib/Vi/QuickFix.pm
Criterion Covered Total %
statement 86 169 50.8
branch 21 74 28.3
condition 12 34 35.2
subroutine 24 37 64.8
pod 0 12 0.0
total 143 326 43.8


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             package Vi::QuickFix;
3 1     1   4711097 use 5.008_000;
  1         9  
  1         72  
4 1     1   12 use strict; use warnings;
  1     1   3  
  1         60  
  1         56  
  1         5  
  1         135  
5             # use Carp;
6              
7             our $VERSION;
8             BEGIN {
9 1     1   217 $VERSION = ('$Revision: 1.134 $' =~ /(\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   8 use constant KEYWORDS => qw(silent sig tie fork);
  1         2  
  1         167  
27              
28             # environment variable(s)
29 1     1   7 use constant VAR_SOURCEFILE => 'VI_QUICKFIX_SOURCEFILE';
  1         5  
  1         1856  
30              
31             BEGIN {{ # space for private variables
32              
33 1     1   3 my $relay = ''; # method of transfer to error file: "sig" or "tie"
  1         1  
34 1         3 my %invocation; # from where was import() called?
35              
36             sub import {
37 4     4   2041 my $class = shift;
38 4         6 my %keywords;
39 4         15 @keywords{ KEYWORDS()} = ();
40 4   100     38 $keywords{ shift()} = 1 while @_ and exists $keywords{ $_[ 0]};
41              
42 4         5 my $filename = shift;
43 4 100       12 make_silent() if $keywords{ silent};
44 4         13 my ( $wanted_relay) = grep $keywords{ $_}, qw( sig tie fork);
45 4   33     9 $relay = $wanted_relay || default_relay();
46 4 100       8 if ( my $reason = relay_obstacle( $relay) ) {
47 1         231 croak( "Cannot use '$relay' method: $reason");
48             }
49 3 50       12 err_open($filename) unless $relay eq 'fork'; # happens in background
50 2 50       78 if ( $relay eq 'tie' ) {
    0          
    0          
51             # if tied, it's tied to ourselves (otherwise obstacle)
52 2 100       11 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         53 (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
  1         1  
66             sub exec_mode {
67 0 0   0 0 0 $exec_mode = shift if @_;
68 0         0 $exec_mode;
69             }
70            
71 1         2 my $silent = 0; # switch off otherwise obligatory warning
72 2     2 0 4 sub make_silent { $silent = 1 }
73 2     2 0 17 sub is_silent { $silent }
74              
75 1         2 my $errfile = 'errors.err'; # name of error file
76 1         2 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 36 $errfile = shift || 'errors.err';
80 3 100       21 $errhandle = IO::File->new( $errfile, '>') or warn(
81             "Can't create error file '$errfile': $!"
82             );
83 2 50       275 $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 2 my $unlink = shift;
92 1 50       12 close $errhandle if $errhandle;
93 1 50 33     99 unlink $errfile if $errfile and $unlink and not -s $errfile;
      33        
94             }
95             }
96              
97             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   393 unless ( is_silent or exec_mode() or $$ != $end_entiteled ) {
      33        
143 0         0 my $invocation_at = "at $invocation{file} line $invocation{line}";
144 0         0 warn "QuickFix ($relay) active $invocation_at\n";
145             }
146             # silently remove objects
147 1         5 make_silent();
148 1 50       3 if ( $relay eq 'tie' ) {
    0          
    0          
149 1         2 untie *STDERR;
150             } elsif ( $relay eq 'sig' ) {
151 0         0 $SIG{ $_} = 'DEFAULT' for qw( __WARN__ __DIE__);
152             } elsif ( $relay eq 'fork' ) {
153 0         0 close STDERR;
154 0         0 wait_kid();
155             }
156             # remove file if created by us and empty
157 1         6 err_clean($$ == $end_entiteled);
158             }
159              
160             }}
161              
162 1     1   6 use constant MINVERS => 5.008001; # minimum perl version for tie method
  1         3  
  1         248  
163             sub relay_obstacle {
164 4   50 4 0 7 my $relay = shift || '';
165 4 50       8 return '' unless $relay eq 'tie';
166 4 50       18 if ( $] < MINVERS ) {
167 0         0 return "perl version is $], must be >= @{[ MINVERS]}";
  0         0  
168             }
169 4 100       13 if ( my $tie_ob = tied *STDERR ) {
170 2         3 my $tieclass = ref $tie_ob;
171 2 100       10 return "STDERR already tied to '$tieclass'" unless
172             $tieclass eq 'Vi::QuickFix::Tee';
173             }
174 3         11 return '';
175             }
176              
177 0 0   0 0 0 sub default_relay { relay_obstacle( 'tie') ? 'sig' : 'tie' }
178              
179             {
180 1     1   7 use Carp;
  1         12  
  1         410  
181             my ($read, $write, $kid);
182             sub fork_relay {
183 0     0 0 0 my $filename = shift;
184 0         0 my $parent = $$;
185 0         0 pipe $read, $write;
186 0 0       0 if ( $kid = fork ) {
187             # parent
188 0         0 close $read;
189 0         0 return $write;
190             } else {
191 0 0       0 Carp::croak "Can't fork: $!" unless defined $kid;
192             # kid
193 0         0 close $write;
194 0         0 err_open($filename);
195 0         0 while ( <$read> ) {
196 0         0 print STDERR $_;
197 0         0 err_out($_);
198             }
199 0         0 err_clean(1);
200 0         0 exit;
201             }
202             }
203              
204 1     1   1342 use POSIX ":sys_wait_h";
  1         12738  
  1         14  
205             sub wait_kid {
206 0     0 0 0 my $x;
207 0         0 do { $x = waitpid -1, WNOHANG } while $x > 0;
  0         0  
208             }
209             }
210              
211             # common destructor method
212             package Vi::QuickFix::Destructor;
213              
214 1     1   1319 use Carp qw( shortmess);
  1         2  
  1         108  
215 1     1   131 BEGIN { our @CARP_NOT = qw( Vi::QuickFix) }
216             sub DESTROY {
217 1     1   458 my $ob = shift;
218 1 50 33     4 return if Vi::QuickFix::is_silent or $^C; # it's a mess under -c
219 0           my $id = $ob->id;
220 0           my $msg = shortmess( "QuickFix $id processing interrupted");
221             # simulate intact QuickFix processing
222 0           Vi::QuickFix::err_out( $msg);
223 0           warn "$msg";
224             }
225              
226             # Class to associate a DESTROY method with sig handlers
227             package Vi::QuickFix::SigHandler;
228 1     1   4 use base qw( Vi::QuickFix::Destructor);
  1         2  
  1         842  
229              
230             # return a chaining handler for __WARN__ or __DIE__
231             sub new {
232 0     0     my $class = shift;
233 0           my $sig = shift;
234 0           my $prev_handler = $SIG{ $sig};
235             my $sub = sub {
236 0 0   0     return $sig unless @_; # backdoor
237 0 0 0       Vi::QuickFix::err_out( @_) unless $sig eq '__DIE__' and _in_eval();
238 0           my $code;
239             # resolve string at call time
240 0 0         if ( $prev_handler ) {
241 0           $code = ref $prev_handler ?
242             $prev_handler :
243 0 0         \ &{ 'main::' . $prev_handler};
244             }
245 0 0         goto &$code if $code;
246 0 0         die @_ if $sig eq '__DIE__';
247 0           warn @_;
248 0           };
249 0           bless $sub, $class; # so we can have a destructor
250             }
251              
252             sub _in_eval {
253 0     0     my $i = -1; # first call with 0
254 0           while ( defined(my $sub = (caller ++ $i)[3]) ) {
255 0 0         return 1 if $sub =~ /^\(eval/;
256             }
257 0           return 0;
258             }
259              
260             sub id {
261 0     0     my $handler = shift;
262 0           $handler->(); # call without args returns __WARN__ or __DIE__
263             }
264              
265             # tie class to tee re-formatted output to an error file
266             package Vi::QuickFix::Tee;
267              
268 1     1   896 use IO::File;
  1         12348  
  1         219  
269 1     1   909 use Tie::Handle;
  1         2521  
  1         31  
270 1     1   9 use base qw( Tie::StdHandle Vi::QuickFix::Destructor);
  1         2  
  1         914  
271              
272             sub WRITE {
273 0     0     my $fh = shift;
274 0           my ( $scalar, $length) = @_;
275 0           Vi::QuickFix::err_out( $scalar);
276 0           $fh->Tie::StdHandle::WRITE( @_);
277             }
278              
279 0     0     sub id { 'STDERR' }
280              
281             1;
282              
283             __END__