File Coverage

blib/lib/filtered.pm
Criterion Covered Total %
statement 138 143 96.5
branch 58 66 87.8
condition 4 6 66.6
subroutine 17 17 100.0
pod n/a
total 217 232 93.5


line stmt bran cond sub pod time code
1 15     15   2259992 use strict;
  15         38  
  15         667  
2 15     15   93 use warnings;
  15         32  
  15         2434  
3              
4             my $pkg2file = sub {
5             my ($pkg) = shift;
6             $pkg =~ s@::@/@g;
7             $pkg .= '.pm';
8             return $pkg;
9             };
10              
11             package filtered; # for Pod::Weaver
12              
13             # ABSTRACT: Apply source filter on external module
14             our $VERSION = 'v0.0.7'; # VERSION
15              
16             package filtered::hook; ## no critic (RequireFilenameMatchesPackage)
17              
18             our $VERSION = 'v0.0.7'; # VERSION
19              
20 15     15   90 use File::Path;
  15         41  
  15         1126  
21 15     15   94 use File::Basename;
  15         35  
  15         20029  
22              
23             my %MYINC;
24              
25             sub new
26             {
27 51     51   173 my ($self, %arg) = @_;
28 51   33     516 my $class = ref($self) || $self;
29 51         439 return bless {
30             _FILTER => $arg{FILTER},
31             }, $class;
32             }
33              
34             # NOTE: To store data in object is probably not good idea because this prohibits re-entrance.
35             sub init
36             {
37 114     114   291 my ($self, $target, $as, $with, $ppi, $prev) = @_;
38              
39 114         433 $self->{_TARGET} = $target;
40 114         221 $self->{_AS} = $as;
41 114         219 $self->{_WITH} = $with;
42 114         329 $self->{_PPI} = $ppi;
43 114         359 $self->{_PREV} = $prev;
44 114         442 return $self;
45             }
46              
47             sub _filter_by_ppi
48             {
49 38     38   78 my ($self, $ref) = @_;
50              
51 38         7030 require PPI::Transform::PackageName;
52             my $trans = PPI::Transform::PackageName->new(
53 740     740   4840 -all => sub { s/^$self->{_TARGET}\b/$self->{_AS}/ }
54 38         656 );
55 38         464 $trans->apply($ref);
56             }
57              
58             sub filtered::hook::INC
59             {
60 114     114   302 my ($self, $filename) = @_;
61 114 50       360 if($pkg2file->($self->{_TARGET}) ne $filename) {
62 0         0 warn "Unexpected loading of $filename against $self->{_TARGET}";
63 0         0 return;
64             }
65              
66 114         371 $self->{_FILENAME} = $filename;
67 114         171 shift @INC; # TODO: Gain robustness # NOTE: Just one time application
68              
69             #print "SELF: $self / FILTER: $self->{_FILTER} / AS: $self->{_AS} / FILENAME: $filename\n";
70              
71             # NOTE: The following part is based on perldoc -f require
72 114 100       545 if (exists $MYINC{$self}{$filename}) {
73             # return 1 in original require
74             return (sub {
75 48 100   48   138 if($_[1]) {
76 24         58 delete $INC{$filename};
77 24 100       120 $INC{$filename} = $self->{_PREV}[1] if($self->{_PREV}[0]);
78 24         43 $_ = "1;\n";
79 24         45 $_[1] = 0;
80 24         179 return 1;
81             } else {
82 24         434 return 0;
83             }
84 24 50       1495 }, 1) if $MYINC{$self}{$filename};
85 0         0 die "Compilation failed in require";
86             }
87 90         131 my ($realfilename,$result);
88             ITER: {
89 90         117 foreach my $prefix (@INC) {
  90         205  
90 171         477 $realfilename = "$prefix/$filename";
91 171 100       5796 if (-f $realfilename) {
92 81         322 $MYINC{$self}{$filename} = $realfilename;
93 81         222 last ITER;
94             }
95             }
96 9         162 die "Can't find $filename in \@INC";
97             }
98              
99 81         122 my ($qr1, $qr2);
100 81         4055 open my $fh, '<', $realfilename;
101 81 100       324 if(defined $self->{_AS}) {
102 57 100       190 if($self->{_PPI}) {
103 38         178 local $/;
104 38         1083 my $content = <$fh>;
105 38         486 close $fh;
106 38         150 undef $fh;
107 38         239 $self->_filter_by_ppi(\$content);
108 10     10   121 open $fh, '<', \$content;
  10         23  
  10         98  
  38         87876  
109             } else {
110 19         355 $qr1 = qr/\b(package\s+)$self->{_TARGET}\b/;
111 19         195 $qr2 = qr/\b$self->{_TARGET}::\b/;
112             }
113             }
114             return (sub {
115 1686     1686   570397 my ($sub, $state) = @_;
116 1686 100 100     10420 if($state == 1) { # Inject filter at the beginning
    100          
    100          
117 81         205 delete $INC{$filename};
118 81 100       472 $INC{$filename} = $self->{_PREV}[1] if($self->{_PREV}[0]);
119 81         311 $_ = 'use '.$self->{_FILTER};
120 81 100       285 if(defined $self->{_WITH}) {
121 12         206 $_ .= ' '.$self->{_WITH};
122             }
123 81 100       459 if(exists $ENV{FILTERED_ROOT}) {
124 15 50       27 if(eval { require Filter::tee; }) {
  15         149  
125 15         27 my $asfile;
126 15 100       49 if(defined($self->{_AS})) {
127 12         27 $asfile = $self->{_AS};
128 12         34 $asfile =~ s@::@/@g;
129 12         28 $asfile .= '.pm';
130             } else {
131 3         8 $asfile = $filename;
132             }
133 15         897 my $dir = dirname($ENV{FILTERED_ROOT}.'/'.$asfile);
134 15 100       1498 File::Path::make_path($dir) if ! -d $dir;
135 15         93 $_ .= "; use Filter::tee '".$ENV{FILTERED_ROOT}.'/'.$asfile."'";
136             } else {
137 0         0 warn 'Ignore environment variable FILTERED_ROOT because Filter::tee is not available';
138             }
139             }
140 81         197 $_ .= ";\n";
141 81         156 $_[1] = 0;
142             } elsif(eof($fh)) {
143 72         559 close $fh;
144 72         1420 return 0;
145             } elsif(defined $self->{_AS} && ! $self->{_PPI}) {
146 441         868 $_ = <$fh>;
147 441         1555 s {$qr1} {${1}$self->{_AS}};
148 441         1415 s {$qr2} {$self->{_AS}::};
149             } else {
150 1092         2045 $_ = <$fh>;
151             }
152 1614         23842 return 1;
153 81         34663 }, 1);
154             }
155              
156             package filtered;
157              
158              
159 15     15   185 use Carp;
  15         34  
  15         1482  
160              
161             my %hook;
162             my $USE_PPI;
163 15     15   48 BEGIN { $USE_PPI = eval { require PPI; }; }
  15         6238  
164              
165             sub import
166             {
167 114     114   152287 my ($class, @args) = @_;
168 114         206 my ($filter, $target, $as, $with);
169 114         238 my $ppi = $USE_PPI;
170 114         167 while(1) {
171 466 100       1166 last unless @args;
172 409 100       1712 if($args[0] eq 'by') {
    100          
    100          
    100          
    100          
173 114         189 shift @args;
174 114         230 $filter = shift @args;
175             } elsif($args[0] eq 'as') {
176 81         121 shift @args;
177 81         160 $as = shift @args;
178             } elsif($args[0] eq 'with') {
179 12         30 shift @args;
180 12         184 $with = shift @args;
181             } elsif($args[0] eq 'use_ppi') {
182 76         108 shift @args;
183 76         149 $ppi = shift @args;
184             } elsif($args[0] eq 'on') {
185 69         94 shift @args;
186 69         125 $target = shift @args;
187             } else {
188 57 100       198 $target = shift @args unless defined $target;
189 57         108 last;
190             }
191             }
192              
193 114 50       321 croak '`by\' must be specified' if ! defined($filter);
194 114 50       327 croak '`on\' or target name must be specified' if ! defined($target);
195 114 100       641 $hook{$filter} = filtered::hook->new(FILTER => $filter) if ! exists $hook{$filter};
196 114 100       363 my $prev = [exists($INC{$pkg2file->($target)}), (exists($INC{$pkg2file->($target)}) ? $INC{$pkg2file->($target)} : '')];
197 114         1262 unshift @INC, $hook{$filter}->init($target, $as, $with, $ppi, $prev);
198 114         382 delete $INC{$pkg2file->($target)};
199 114 100       26799 if(!defined eval "require $target") {
200 18         4391 delete $INC{$hook{$filter}{_FILENAME}}; # For error in internal require;
201 18 100       80 $INC{$hook{$filter}{_FILENAME}} = $prev->[1] if $prev->[0];
202 18         487 croak "Can't load $target by $@";
203             }
204 96 100       18703 if(defined $as) {
205 81         333 @_ = ($as, @args);
206             } else {
207 15         59 @_ = ($target, @args);
208             }
209             {
210 15     15   78 no strict 'refs'; ## no critic (ProhibitNoStrict)
  15         30  
  15         612  
  96         221  
211 15     15   91 no warnings 'once';
  15         25  
  15         4342  
212 96         1287 my $import = $_[0]->can('import');
213 96 50       286 if(defined $import) {
    0          
214 96         4645 goto &$import;
215             } elsif ($_[0]->isa('Exporter')) {
216 0           $_[0]->export_to_level(1, @_);
217             }
218             }
219             }
220              
221             1;
222              
223             __END__