File Coverage

blib/lib/Module/Reader.pm
Criterion Covered Total %
statement 151 190 79.4
branch 72 112 64.2
condition 25 51 49.0
subroutine 36 47 76.6
pod 10 11 90.9
total 294 411 71.5


line stmt bran cond sub pod time code
1             package Module::Reader;
2 3     3   63696 BEGIN { require 5.006 }
3 3     3   13 use strict;
  3         6  
  3         78  
4 3     3   13 use warnings;
  3         4  
  3         168  
5              
6             our $VERSION = '0.003_001';
7             $VERSION = eval $VERSION;
8              
9 3     3   21 use Exporter (); BEGIN { *import = \&Exporter::import }
  3     3   4  
  3         81  
  3         181  
10             our @EXPORT_OK = qw(module_content module_handle);
11             our %EXPORT_TAGS = (all => [@EXPORT_OK]);
12              
13 3     3   15 use File::Spec;
  3         4  
  3         89  
14 3     3   13 use Scalar::Util qw(reftype refaddr openhandle);
  3         4  
  3         377  
15 3     3   17 use Carp;
  3         4  
  3         205  
16 3     3   15 use Config ();
  3         5  
  3         74  
17 3     3   1790 use Errno qw(EACCES);
  3         3872  
  3         561  
18             use constant _PMC_ENABLED => !(
19 24         368 exists &Config::non_bincompat_options ? grep { $_ eq 'PERL_DISABLE_PMC' } Config::non_bincompat_options()
20 3 50       50 : $Config::Config{ccflags} =~ /(?:^|\s)-DPERL_DISABLE_PMC\b/
21 3     3   19 );
  3         18  
22 3   33 3   17 use constant _VMS => $^O eq 'VMS' && !!require VMS::Filespec;
  3         4  
  3         258  
23 3     3   16 use constant _WIN32 => $^O eq 'MSWin32';
  3         37  
  3         430  
24 3         4 use constant _FAKE_FILE_FORMAT => do {
25 3   50     295 (my $uvx = $Config::Config{uvxformat}||'') =~ tr/"\0//d;
26 3   50     18 $uvx ||= 'lx';
27 3         270 "/loader/0x%$uvx/%s"
28 3     3   16 };
  3         4  
29 3 50   3   17 use constant _OPEN_LAYERS => "$]" >= 5.008 ? ':' : '';
  3         4  
  3         3814  
30              
31             sub _mod_to_file {
32 35     35   38 my $module = shift;
33 35         92 (my $file = "$module.pm") =~ s{::}{/}g;
34 35         77 $file;
35             }
36              
37             sub module_content {
38 18   100 18 1 5762 my $opts = ref $_[-1] eq 'HASH' && pop @_ || {};
39 18         25 my $module = shift;
40 18 50       37 $opts->{inc} = [@_]
41             if @_;
42 18         63 __PACKAGE__->new($opts)->module($module)->content;
43             }
44              
45             sub module_handle {
46 0   0 0 1 0 my $opts = ref $_[-1] eq 'HASH' && pop @_ || {};
47 0         0 my $module = shift;
48 0 0       0 $opts->{inc} = [@_]
49             if @_;
50 0         0 __PACKAGE__->new($opts)->module($module)->handle;
51             }
52              
53             sub new {
54 83     83 0 35934 my $class = shift;
55 83         95 my %options;
56 83 100 66     370 if (@_ == 1 && ref $_[-1]) {
    50          
57 18         19 %options = %{(pop)};
  18         66  
58             }
59             elsif (@_ % 2 == 0) {
60 65         159 %options = @_;
61             }
62             else {
63 0         0 croak "Expected hash ref, or key value pairs. Got ".@_." arguments.";
64             }
65              
66 83   100     204 $options{inc} ||= \@INC;
67             $options{found} = \%INC
68 83 50 66     251 if exists $options{found} && $options{found} eq 1;
69             $options{pmc} = _PMC_ENABLED
70 83 100       189 if !exists $options{pmc};
71             $options{open} = 1
72 83 50       167 if !exists $options{open};
73 83         215 bless \%options, $class;
74             }
75              
76             sub module {
77 35     35 1 109 my ($self, $module) = @_;
78 35         63 $self->file(_mod_to_file($module));
79             }
80              
81             sub modules {
82 0     0 1 0 my ($self, $module) = @_;
83 0         0 $self->files(_mod_to_file($module));
84             }
85              
86             sub file {
87 83     83 1 223 my ($self, $file) = @_;
88 83         149 $self->_find($file);
89             }
90              
91             sub files {
92 0     0 1 0 my ($self, $file) = @_;
93 0         0 $self->_find($file, 1);
94             }
95              
96             sub _searchable {
97 83     83   85 my $file = shift;
98 83 100       723 File::Spec->file_name_is_absolute($file) ? 0
    50          
99             : _WIN32 && $file =~ m{^\.\.?[/\\]} ? 0
100             : $file =~ m{^\.\.?/} ? 0
101             : 1
102             }
103              
104             sub _find {
105 83     83   85 my ($self, $file, $all) = @_;
106              
107 83 100       138 if (!_searchable($file)) {
108 16         33 my $open = $self->_open_file($file);
109 16 100       64 return $open
110             if $open;
111 4         563 croak "Can't locate $file";
112             }
113              
114 67         83 my @found;
115 67         67 eval {
116 67 100       184 if (my $found = $self->{found}) {
117 17 50       43 if (defined( my $full = $found->{$file} )) {
118 17 100       53 my $open = length ref $full ? $self->_open_ref($full, $file)
119             : $self->_open_file($full, $file);
120 17 100       60 push @found, $open
121             if $open;
122             }
123             }
124             };
125 67 50       117 if (!$all) {
126 67 100       138 return $found[0]
127             if @found;
128 54 50       104 die $@
129             if $@;
130             }
131 54         63 my $search = $self->{inc};
132 54         80 for my $inc (@$search) {
133 75         63 my $open;
134 75         67 eval {
135 75 100       138 if (!length ref $inc) {
136 41         40 my $full = _VMS ? VMS::Filespec::unixpath($inc) : $inc;
137 41         203 $full =~ s{/?$}{/};
138 41         95 $full .= $file;
139 41         79 $open = $self->_open_file($full, $file, $inc);
140             }
141             else {
142 34         60 $open = $self->_open_ref($inc, $file);
143             }
144 66 100       173 push @found, $open
145             if $open;
146             };
147 75 50       141 if (!$all) {
148 75 100       245 return $found[0]
149             if @found;
150 30 100       85 die $@
151             if $@;
152             }
153             }
154 0 0       0 croak "Can't locate $file"
155             if !$all;
156 0         0 return @found;
157             }
158              
159             sub _open_file {
160 73     73   93 my ($self, $full, $file, $inc) = @_;
161 73 100       139 $file = $full
162             if !defined $file;
163 73 100 66     427 for my $try (
164             ($self->{pmc} && $file =~ /\.pm\z/ ? $full.'c' : ()),
165             $full,
166             ) {
167 98         125 my $pmc = $full ne $try;
168             next
169 98 100 66     1952 if -e $try ? (-d _ || -b _) : $! != EACCES;
    100          
170              
171 49 50       1596 if (!$self->{open} ? -e _ : open my $fh, '<'._OPEN_LAYERS, $try) {
    50          
172 49 50       325 return Module::Reader::File->new(
    100          
173             filename => $file,
174             ($fh ? (raw_filehandle => $fh) : ()),
175             found_file => $full,
176             disk_file => $try,
177             is_pmc => $pmc,
178             (defined $inc ? (inc_entry => $inc) : ()),
179             );
180             }
181 0 0       0 croak "Can't locate $file: $full: $!"
182             unless $pmc;
183             }
184 24         45 return;
185             }
186              
187             sub _open_ref {
188 35     35   42 my ($self, $inc, $file) = @_;
189              
190 35         29 my @cb;
191             {
192             # strings in arrayrefs are taken as sub names relative to main
193 35         31 package
194             main;
195 3     3   20 no strict 'refs';
  3         4  
  3         135  
196 3     3   20 no warnings 'uninitialized';
  3         4  
  3         1171  
197 35 100       194 @cb = defined Scalar::Util::blessed $inc ? $inc->INC($file)
    100          
198             : ref $inc eq 'ARRAY' ? $inc->[0]->($inc, $file)
199             : $inc->($inc, $file);
200             }
201              
202             return
203 26 100       1275 unless length ref $cb[0];
204              
205 23         119 my $fake_file = sprintf _FAKE_FILE_FORMAT, refaddr($inc), $file;
206              
207 23         23 my $fh;
208             my $cb;
209 0         0 my $cb_options;
210              
211 23 100 66     110 if (reftype $cb[0] eq 'GLOB' && openhandle $cb[0]) {
212 9         14 $fh = shift @cb;
213             }
214              
215 23 100 100     107 if ((reftype $cb[0]||'') eq 'CODE') {
    100          
216 12         12 $cb = $cb[0];
217             # only one or zero callback options will be passed
218 12 50       24 $cb_options = @cb > 1 ? [ $cb[1] ] : undef;
219             }
220             elsif (!$fh) {
221 2         5 return;
222             }
223 21 100       117 return Module::Reader::File->new(
    100          
    50          
224             filename => $file,
225             found_file => $fake_file,
226             inc_entry => $inc,
227             (defined $fh ? (raw_filehandle => $fh) : ()),
228             (defined $cb ? (read_callback => $cb) : ()),
229             (defined $cb_options ? (read_callback_options => $cb_options) : ()),
230             );
231             }
232              
233 0     0 1 0 sub inc { $_[0]->{inc} }
234 0     0 1 0 sub found { $_[0]->{found} }
235 0     0 1 0 sub pmc { $_[0]->{pmc} }
236 0     0 1 0 sub open { $_[0]->{open} }
237              
238             {
239             package Module::Reader::File;
240 3   33 3   18 use constant _OPEN_STRING => "$]" >= 5.008 || (require IO::String, 0);
  3         4  
  3         217  
241 3     3   16 use Carp 'croak';
  3         4  
  3         2289  
242              
243             sub new {
244 70     70   303 my ($class, %opts) = @_;
245 70         96 my $filename = $opts{filename};
246 70 100 33     651 if (!exists $opts{module} && $opts{filename}
      66        
247             && $opts{filename} =~ m{\A(\w+(?:/\w+)?)\.pm\z}) {
248 58         136 my $module = $1;
249 58         76 $module =~ s{/}{::}g;
250 58         104 $opts{module} = $module;
251             }
252 70         247 bless \%opts, $class;
253             }
254              
255 0     0   0 sub filename { $_[0]->{filename} }
256 0     0   0 sub module { $_[0]->{module} }
257 1     1   5 sub found_file { $_[0]->{found_file} }
258 12     12   54 sub disk_file { $_[0]->{disk_file} }
259 36     36   101 sub is_pmc { $_[0]->{is_pmc} }
260 60     60   434 sub inc_entry { $_[0]->{inc_entry} }
261 9     9   12 sub read_callback { $_[0]->{read_callback} }
262 0     0   0 sub read_callback_options { $_[0]->{read_callback_options} }
263             sub raw_filehandle {
264 9 0 33 9   26 $_[0]->{raw_filehandle} ||= !$_[0]->{disk_file} ? undef : do {
265             open my $fh, '<'.Module::Reader::_OPEN_LAYERS, $_[0]->{disk_file}
266 0 0       0 or croak "Can't locate $_[0]->{disk_file}";
267             };
268             }
269              
270             sub content {
271 9     9   15 my $self = shift;
272             return $self->{content}
273 9 50       19 if exists $self->{content};
274 9         15 my $fh = $self->raw_filehandle;
275 9         16 my $cb = $self->read_callback;
276 9 50 33     39 if ($fh && !$cb) {
277 9         20 local $/;
278 9         87 return scalar <$fh>;
279             }
280 0 0         my @params = @{$self->read_callback_options||[]};
  0            
281 0           my $content = '';
282 0           while (1) {
283 0 0         local $_ = $fh ? <$fh> : '';
284 0 0         $_ = ''
285             if !defined;
286             # perlfunc/require says that the first parameter will be a reference the
287             # sub itself. this is wrong. 0 will be passed.
288 0 0         last if !$cb->(0, @params);
289 0           $content .= $_;
290             }
291 0           return $self->{content} = $content;
292             }
293              
294             sub handle {
295 0     0     my $self = shift;
296 0           my $fh = $self->raw_filehandle;
297 0 0 0       if ($fh && !$self->read_callback && -f $fh) {
      0        
298 0 0         open my $dup, '<&', $fh
299             or croak "can't dup file handle: $!";
300 0           return $dup;
301             }
302 0           my $content = $self->content;
303 0           if (_OPEN_STRING) {
304 0           open my $fh, '<', \$content;
305 0           return $fh;
306             }
307             else {
308             return IO::String->new($content);
309             }
310             }
311             }
312              
313             1;
314              
315             __END__