File Coverage

blib/lib/Filter/Simple.pm
Criterion Covered Total %
statement 101 108 93.5
branch 27 42 64.2
condition 2 6 33.3
subroutine 17 18 94.4
pod 0 6 0.0
total 147 180 81.6


line stmt bran cond sub pod time code
1             package Filter::Simple;
2              
3 6     6   7141 use Text::Balanced ':ALL';
  6         98702  
  6         1341  
4              
5 6     6   55 use vars qw{ $VERSION @EXPORT };
  6         15  
  6         362  
6              
7             $VERSION = '0.94';
8              
9 6     6   1882 use Filter::Util::Call;
  6         4353  
  6         327  
10 6     6   38 use Carp;
  6         12  
  6         8290  
11              
12             @EXPORT = qw( FILTER FILTER_ONLY );
13              
14              
15             sub import {
16 6 50   6   84 if (@_>1) { shift; goto &FILTER }
  0         0  
  0         0  
17 6         26 else { *{caller()."::$_"} = \&$_ foreach @EXPORT }
  12         645  
18             }
19              
20             sub fail {
21 0     0 0 0 croak "FILTER_ONLY: ", @_;
22             }
23              
24             my $exql = sub {
25             my @bits = extract_quotelike $_[0], qr//;
26             return unless $bits[0];
27             return \@bits;
28             };
29              
30             my $ncws = qr/\s+/;
31             my $comment = qr/(?
32             my $ws = qr/(?:$ncws|$comment)+/;
33             my $id = qr/\b(?!([ysm]|q[rqxw]?|tr)\b)\w+/;
34             my $EOP = qr/\n\n|\Z/;
35             my $CUT = qr/\n=cut.*$EOP/;
36             my $pod_or_DATA = qr/
37             ^=(?:head[1-4]|item) .*? $CUT
38             | ^=pod .*? $CUT
39             | ^=for .*? $CUT
40             | ^=begin .*? $CUT
41             | ^__(DATA|END)__\r?\n.*
42             /smx;
43             my $variable = qr{
44             [\$*\@%]\s*
45             \{\s*(?!::)(?:\d+|[][&`'#+*./|,";%=~:?!\@<>()-]|\^[A-Z]?)\}
46             | (?:\$#?|[*\@\%]|\\&)\$*\s*
47             (?: \{\s*(?:\^(?=[A-Z_]))?(?:\w|::|'\w)*\s*\}
48             | (?:\^(?=[A-Z_]))?(?:\w|::|'\w)*
49             | (?=\{) # ${ block }
50             )
51             )
52             | \$\s*(?!::)(?:\d+|[][&`'#+*./|,";%=~:?!\@<>()-]|\^[A-Z]?)
53             }x;
54              
55             my %extractor_for = (
56             quotelike => [ $ws, $variable, $id, { MATCH => \&extract_quotelike } ],
57             regex => [ $ws, $pod_or_DATA, $id, $exql ],
58             string => [ $ws, $pod_or_DATA, $id, $exql ],
59             code => [ $ws, { DONT_MATCH => $pod_or_DATA }, $variable,
60             $id, { DONT_MATCH => \&extract_quotelike } ],
61             code_no_comments
62             => [ { DONT_MATCH => $comment },
63             $ncws, { DONT_MATCH => $pod_or_DATA }, $variable,
64             $id, { DONT_MATCH => \&extract_quotelike } ],
65             executable => [ $ws, { DONT_MATCH => $pod_or_DATA } ],
66             executable_no_comments
67             => [ { DONT_MATCH => $comment },
68             $ncws, { DONT_MATCH => $pod_or_DATA } ],
69             all => [ { MATCH => qr/(?s:.*)/ } ],
70             );
71              
72             my %selector_for = (
73             all => sub { my ($t)=@_; sub{ $_=$$_; $t->(@_); $_} },
74             executable=> sub { my ($t)=@_; sub{ref() ? $_=$$_ : $t->(@_); $_} },
75             quotelike => sub { my ($t)=@_; sub{ref() && do{$_=$$_; $t->(@_)}; $_} },
76             regex => sub { my ($t)=@_;
77             sub{ref() or return $_;
78             my ($ql,undef,$pre,$op,$ld,$pat) = @$_;
79             return $_->[0] unless $op =~ /^(qr|m|s)/
80             || !$op && ($ld eq '/' || $ld eq '?');
81             $_ = $pat;
82             $t->(@_);
83             $ql =~ s/^(\s*\Q$op\E\s*\Q$ld\E)\Q$pat\E/$1$_/;
84             return "$pre$ql";
85             };
86             },
87             string => sub { my ($t)=@_;
88             sub{ref() or return $_;
89             local *args = \@_;
90             my ($pre,$op,$ld1,$str1,$rd1,$ld2,$str2,$rd2,$flg) = @{$_}[2..10];
91             return $_->[0] if $op =~ /^(qr|m)/
92             || !$op && ($ld1 eq '/' || $ld1 eq '?');
93             if (!$op || $op eq 'tr' || $op eq 'y') {
94             local *_ = \$str1;
95             $t->(@args);
96             }
97             if ($op =~ /^(tr|y|s)/) {
98             local *_ = \$str2;
99             $t->(@args);
100             }
101             my $result = "$pre$op$ld1$str1$rd1";
102             $result .= $ld2 if $ld1 =~ m/[[({<]/; #])}>
103             $result .= "$str2$rd2$flg";
104             return $result;
105             };
106             },
107             );
108              
109              
110             sub gen_std_filter_for {
111 5     5 0 12 my ($type, $transform) = @_;
112             return sub {
113 5     5   13 my $instr;
114 5         12 local @components;
115 5         27 for (extract_multiple($_,$extractor_for{$type})) {
116 291 100       38090 if (ref()) { push @components, $_; $instr=0 }
  35 100       70  
  35         75  
117 218         405 elsif ($instr) { $components[-1] .= $_ }
118 38         91 else { push @components, $_; $instr=1 }
  38         101  
119             }
120 5 100       45 if ($type =~ /^code/) {
121 3         9 my $count = 0;
122 3         53 local $placeholder = qr/\Q$;\E(.{4})\Q$;\E/s;
123 3         35 my $extractor = qr/\Q$;\E(.{4})\Q$;\E/s;
124             $_ = join "",
125 3 100       13 map { ref $_ ? $;.pack('N',$count++).$; : $_ }
  41         167  
126             @components;
127 3         15 @components = grep { ref $_ } @components;
  41         92  
128 3         22 $transform->(@_);
129 3         47 s/$extractor/${$components[unpack('N',$1)]}/g;
  20         172  
130             }
131             else {
132 2         12 my $selector = $selector_for{$type}->($transform);
133 2         10 $_ = join "", map $selector->(@_), @components;
134             }
135             }
136 5         32 };
137              
138             sub FILTER (&;$) {
139 3     3 0 783 my $caller = caller;
140 3         10 my ($filter, $terminator) = @_;
141 6     6   58 no warnings 'redefine';
  6         20  
  6         1447  
142 3         11 *{"${caller}::import"} = gen_filter_import($caller,$filter,$terminator);
  3         15  
143 3         17 *{"${caller}::unimport"} = gen_filter_unimport($caller);
  3         19  
144             }
145              
146             sub FILTER_ONLY {
147 3     3 0 244 my $caller = caller;
148 3         16 while (@_ > 1) {
149 5         16 my ($what, $how) = splice(@_, 0, 2);
150             fail "Unknown selector: $what"
151 5 50       29 unless exists $extractor_for{$what};
152 5 50       21 fail "Filter for $what is not a subroutine reference"
153             unless ref $how eq 'CODE';
154 5         15 push @transforms, gen_std_filter_for($what,$how);
155             }
156 3         6 my $terminator = shift;
157              
158             my $multitransform = sub {
159 3     3   9 foreach my $transform ( @transforms ) {
160 5         17 $transform->(@_);
161             }
162 3         8 };
163 6     6   45 no warnings 'redefine';
  6         15  
  6         3292  
164 3         15 *{"${caller}::import"} =
  3         13  
165             gen_filter_import($caller,$multitransform,$terminator);
166 3         11 *{"${caller}::unimport"} = gen_filter_unimport($caller);
  3         36  
167             }
168              
169             my $ows = qr/(?:[ \t]+|#[^\n]*)*/;
170              
171             sub gen_filter_import {
172 6     6 0 16 my ($class, $filter, $terminator) = @_;
173 6         12 my %terminator;
174 6         13 my $prev_import = *{$class."::import"}{CODE};
  6         46  
175             return sub {
176 6     6   114 my ($imported_class, @args) = @_;
177 6         274 my $def_terminator =
178             qr/^(?:\s*no\s+$imported_class\s*;$ows|__(?:END|DATA)__)\r?$/;
179 6 50 0     32 if (!defined $terminator) {
    0          
    0          
    0          
180 6         18 $terminator{terminator} = $def_terminator;
181             }
182             elsif (!ref $terminator || ref $terminator eq 'Regexp') {
183 0         0 $terminator{terminator} = $terminator;
184             }
185             elsif (ref $terminator ne 'HASH') {
186 0         0 croak "Terminator must be specified as scalar or hash ref"
187             }
188             elsif (!exists $terminator->{terminator}) {
189 0         0 $terminator{terminator} = $def_terminator;
190             }
191             filter_add(
192             sub {
193 9     9   91 my ($status, $lastline);
194 9         18 my $count = 0;
195 9         20 my $data = "";
196 9         71 while ($status = filter_read()) {
197 68 50       152 return $status if $status < 0;
198 68 100 66     436 if ($terminator{terminator} &&
199             m/$terminator{terminator}/) {
200 3         9 $lastline = $_;
201 3         8 $count++;
202 3         9 last;
203             }
204 65         136 $data .= $_;
205 65         97 $count++;
206 65         209 $_ = "";
207             }
208 9 100       3302 return $count if not $count;
209 6         13 $_ = $data;
210 6 50       43 $filter->($imported_class, @args) unless $status < 0;
211 6 100       73 if (defined $lastline) {
212 3 50       42 if (defined $terminator{becomes}) {
    50          
213 0         0 $_ .= $terminator{becomes};
214             }
215             elsif ($lastline =~ $def_terminator) {
216 3         12 $_ .= $lastline;
217             }
218             }
219 6         1746 return $count;
220             }
221 6         50 );
222 6 100       216 if ($prev_import) {
    100          
223 1         4 goto &$prev_import;
224             }
225             elsif ($class->isa('Exporter')) {
226 1         160 $class->export_to_level(1,@_);
227             }
228             }
229 6         34 }
230              
231             sub gen_filter_unimport {
232 6     6 0 14 my ($class) = @_;
233             return sub {
234 2     2   26 filter_del();
235 2 50       2873 goto &$prev_unimport if $prev_unimport;
236             }
237 6         24 }
238              
239             1;
240              
241             __END__