File Coverage

lib/Path/ScanINC.pm
Criterion Covered Total %
statement 91 138 65.9
branch 24 50 48.0
condition 6 12 50.0
subroutine 22 24 91.6
pod 4 5 80.0
total 147 229 64.1


line stmt bran cond sub pod time code
1 5     5   389357 use 5.008; # utf8
  5         19  
  5         254  
2 5     5   31 use strict;
  5         10  
  5         196  
3 5     5   44 use warnings;
  5         12  
  5         198  
4 5     5   19950 use utf8;
  5         64  
  5         36  
5              
6             package Path::ScanINC;
7              
8             our $VERSION = '1.000002';
9              
10             # ABSTRACT: Emulate Perls internal handling of @INC.
11              
12             our $AUTHORITY = 'cpan:KENTNL'; # AUTHORITY
13              
14             # Sub Lazy-Aliases
15 5     5   5115 use subs 'inc';
  5         136  
  5         26  
16 5     5   4503 use Class::Tiny qw(inc immutable);
  5         18793  
  5         38  
17 5     5   4635 use Try::Tiny qw( try catch );
  5         5810  
  5         392  
18 5     5   36 use Scalar::Util qw( blessed reftype );
  5         9  
  5         624  
19 5     5   54 use Carp qw( croak );
  5         11  
  5         378  
20 5     5   6910 use Path::Tiny qw( path );
  5         153866  
  5         10998  
21              
22             ## no critic (Bangs::ProhibitDebuggingModules)
23 3     3   3454 sub __pp { require Data::Dump; goto \&Data::Dump::pp; }
  3         27857  
24             ## no critic (RequireArgUnpacking)
25 3     3   2789 sub __croakf { require Carp; @_ = ( sprintf $_[0], splice @_, 1 ); goto \&Carp::croak; }
  3         31  
  3         997  
26             ## use critic
27              
28              
29              
30              
31              
32              
33              
34              
35              
36              
37              
38              
39              
40              
41              
42              
43              
44              
45             sub _bad_param {
46 2     2   9 my ( $self, $name, $expected, $got ) = @_;
47 2         5 my $format =
48             qq[Initialization parameter '%s' to \$object->new( ) ( %s->new() ) expects %s.\n] . qq[\tYou gave \$object->new( %s => %s )];
49 2         27 return __croakf( $format, $name, blessed($self), $expected, $name, __pp($got) );
50             }
51              
52             sub _fix_immutable {
53 15     15   26 my ($self) = @_;
54 15 100       74 if ( exists $self->{immutable} ) {
55 6 100       26 return $self->_bad_param( 'immutable', 'undef/a true value', $self->{immutable} ) if ref $self->{immutable};
56 5         16 $self->{immutable} = !!( $self->{immutable} );
57             }
58 14         26 return;
59             }
60              
61             sub _fix_inc {
62 14     14   26 my ($self) = @_;
63 14 100       56 if ( exists $self->{inc} ) {
64             return $self->_bad_param( 'inc', 'an array-reference', $self->{inc} )
65 8 100   2   75 if not try { scalar $self->{inc}->[0]; 1 } catch { undef };
  8         452  
  7         23  
  1         18  
66             }
67 13 100       576 if ( $self->immutable ) {
68 4 100       43 if ( exists $self->{inc} ) {
69 2         5 $self->{inc} = [ @{ $self->{inc} } ];
  2         11  
70             }
71             else {
72 2         27 $self->{inc} = [@INC];
73             }
74             }
75 13         86 return;
76             }
77              
78              
79              
80              
81              
82              
83              
84              
85              
86             sub BUILD {
87 15     15 0 26891 my ( $self, ) = @_;
88 15         53 $self->_fix_immutable;
89 14         41 $self->_fix_inc;
90 13         33 return;
91             }
92              
93              
94              
95              
96              
97              
98              
99              
100              
101              
102              
103              
104              
105              
106              
107              
108              
109              
110              
111              
112              
113              
114              
115              
116              
117              
118              
119             sub inc {
120 24     24   8928 my ( $self, ) = @_;
121 24 100       111 return @INC if ( not exists $self->{inc} );
122 19         24 return @{ $self->{inc} };
  19         110  
123             }
124              
125             sub _pm_inc_path {
126 0     0   0 my ( undef, @path_parts ) = @_;
127 0         0 return join q[/], @path_parts;
128             }
129              
130             # This method deals with the fact there are refs in @INC, and they have special magic behaviour.
131             #
132             # Perl itself, simply invokes special behaviours on those refs, passing the path given in `require`
133             #
134             # So in comparison
135             #
136             # $self->_ref_expand( $ref, @query )
137             #
138             # Invokes those methods, after converting @query to notional format.
139             #
140              
141             sub _ref_expand {
142 0     0   0 my ( $self, $ref, @query ) = @_;
143              
144             # See perldoc perlfunc / require
145 0 0       0 if ( blessed($ref) ) {
146 0         0 my (@result) = $ref->INC( $self->_pm_inc_path(@query) );
147 0 0       0 if ( not @result ) {
148 0         0 return [ undef, ];
149             }
150 0         0 return [ 1, @result ];
151             }
152 0 0       0 if ( 'CODE' eq reftype($ref) ) {
153 0         0 my (@result) = $ref->( $ref, $self->_pm_inc_path(@query) );
154 0 0       0 if ( not @result ) {
155 0         0 return [ undef, ];
156             }
157 0         0 return [ 1, @result ];
158             }
159 0 0       0 if ( 'ARRAY' eq reftype($ref) ) {
160 0         0 my $code = $ref->[0];
161 0         0 my (@result) = $code->( $ref, $self->_pm_inc_path(@query) );
162 0 0       0 if ( not @result ) {
163 0         0 return [ undef, ];
164             }
165 0         0 return [ 1, @result ];
166             }
167             ## no critic (RequireInterpolationOfMetachars)
168              
169 0         0 __croakf( 'Unknown type of ref in @INC not supported: %s', reftype($ref) );
170 0         0 return [ undef, ];
171             }
172              
173              
174              
175              
176              
177              
178              
179              
180              
181              
182              
183              
184              
185              
186              
187              
188              
189              
190              
191              
192              
193              
194              
195              
196              
197              
198              
199              
200              
201              
202              
203              
204              
205              
206              
207              
208              
209              
210              
211              
212              
213              
214              
215              
216              
217              
218              
219              
220              
221              
222              
223              
224              
225              
226              
227             sub first_file {
228 1     1 1 11 my ( $self, @args ) = @_;
229              
230 1         4 for my $path ( $self->inc ) {
231 1 50       42 if ( ref $path ) {
232 0         0 my $result = $self->_ref_expand( $path, @args );
233 0 0       0 if ( $result->[0] ) {
234 0         0 shift @{$result};
  0         0  
235 0         0 return $result;
236             }
237 0         0 next;
238             }
239 1         4 my $fullpath = path($path)->child(@args);
240 1 50 33     49 if ( -e $fullpath and not -d $fullpath ) {
241 1         82 return $fullpath;
242             }
243             }
244 0         0 return;
245             }
246              
247              
248              
249              
250              
251              
252              
253              
254              
255              
256              
257              
258              
259              
260              
261              
262              
263              
264              
265              
266              
267              
268              
269              
270              
271              
272              
273              
274              
275              
276              
277              
278              
279              
280             sub all_files {
281 1     1 1 2608 my ( $self, @args ) = @_;
282              
283 1         3 my @out;
284 1         3 for my $path ( $self->inc ) {
285 3 50       10 if ( ref $path ) {
286 0         0 my $result = $self->_ref_expand( $path, @args );
287 0 0       0 if ( $result->[0] ) {
288 0         0 shift @{$result};
  0         0  
289 0         0 push @out, $result;
290             }
291 0         0 next;
292             }
293 3         8 my $fullpath = path($path)->child(@args);
294 3 50 33     157 if ( -e $fullpath and not -d $fullpath ) {
295 3         174 push @out, $fullpath;
296             }
297             }
298 1         6 return @out;
299             }
300              
301              
302              
303              
304              
305              
306              
307             sub first_dir {
308 2     2 1 9719 my ( $self, @args ) = @_;
309              
310 2         8 for my $path ( $self->inc ) {
311 4 50       49 if ( ref $path ) {
312 0         0 my $result = $self->_ref_expand( $path, @args );
313 0 0       0 if ( $result->[0] ) {
314 0         0 shift @{$result};
  0         0  
315 0         0 return $result;
316             }
317 0         0 next;
318             }
319 4         16 my $fullpath = path($path)->child(@args);
320 4 100 66     168 if ( -e $fullpath and -d $fullpath ) {
321 2         127 return $fullpath;
322             }
323             }
324 0         0 return;
325             }
326              
327              
328              
329              
330              
331              
332              
333             sub all_dirs {
334 2     2 1 1688 my ( $self, @args ) = @_;
335 2         4 my @out;
336 2         5 for my $path ( $self->inc ) {
337 6 50       85 if ( ref $path ) {
338 0         0 my $result = $self->_ref_expand( $path, @args );
339 0 0       0 if ( $result->[0] ) {
340 0         0 shift @{$result};
  0         0  
341 0         0 push @out, $result;
342             }
343 0         0 next;
344             }
345 6         22 my $fullpath = path($path)->child(@args);
346 6 100 66     240 if ( -e $fullpath and -d $fullpath ) {
347 2         120 push @out, $fullpath;
348             }
349             }
350 2         32 return @out;
351             }
352              
353             1;
354              
355             __END__
356              
357             =pod
358              
359             =encoding UTF-8
360              
361             =head1 NAME
362              
363             Path::ScanINC - Emulate Perls internal handling of @INC.
364              
365             =head1 VERSION
366              
367             version 1.000002
368              
369             =head1 SYNOPSIS
370              
371             The Aim of this module is to fully implement everything Perl does with C<@INC>, to be feature compatible with it, including
372             the behavior with regard to C<sub refs> in C<@INC>.
373              
374             use Path::ScanINC;
375              
376             # Normal usage.
377             my $inc = Path::ScanINC->new( );
378              
379             # In case you need something that isn't @INC
380             # but works like it
381              
382             my $inc = Path::ScanINC->new( inc => \@INC );
383              
384             # Freeze the value of @INC at the time of object instantiation
385             # with regard to behaviour so later changes to @INC have no effect
386              
387             my $inc = Path::ScanINC->new( immutable => 1 );
388              
389             # Return the first file in @INC that matches.
390              
391             my $file = $inc->first_file('Path', 'ScanINC.pm' );
392              
393             # Find all possible versions of modules in @INC
394             my ( @files ) = $inc->all_files('Path', 'ScanINC.pm');
395              
396             # Try to discover a File::ShareDir 'module' root.
397             my $dir = $inc->first_dir('auto','share','module');
398              
399             # Should return the same as File::ShareDir::module_dir('Path::ScanINC')
400             # ( assuming such a directory existed, which there is presently no plans of )
401             my $dir = $inc->first_dir('auto','share','module','Path-ScanINC');
402              
403              
404             # Find All File::ShareDir roots in @INC
405             my ( @dirs ) = $inc->all_dirs('auto', 'share');
406              
407             =head1 REF SUPPORT IN @INC
408              
409             This module has elemental support for discovery of results in C<@INC> using C<CODE>/C<ARRAY>/C<BLESSED> entries in
410             C<@INC>. However, due to a limitation as to how C<perl> itself implements this functionality, the best we can do at present
411             is simply return what the above are expected to return. This means if you have any of the above ref-types in C<@INC>,
412             and one of those returns C<a true value>, you'll get handed back an C<ARRAY> reference instead of the file you were
413             expecting.
414              
415             Fortunately, C<@INC> barely ever has refs in it. But in the event you I<need> to work with refs in C<@INC> and you
416             expect that those refs will return C<true>, you have to pick one of two options, either :
417              
418             =over 4
419              
420             =item a. Write your code to work with the C<array-ref> returned by the respective reference on a match
421              
422             =item b. Use the C<all_> family of methods and try pretending that there are no C<array-refs> in the list it returns.
423              
424             =back
425              
426             Its possible in a future release we may have better choices how to handle this situation in future, but don't bet on it.
427              
428             Given that the API as defined by Perl mandates C<code-ref>'s return lists containing C<file-handles> or iterative
429             C<code-ref>'s , not actual files, the best I can foresee at this time we'd be able to do to make life easier for you is
430             creating a fake library somewhere in a C<tempdir> and stuffing the result of the C<code-ref>'s into files in that directory
431             prior to returning a path to the generated file.
432              
433             ( And it also tells me that they have to be "Real" file handles, not tied or blessed ones, so being able to ask a
434             C<filehandle> what file it represents is equally slim.... if that is of course what you require )
435              
436             For more details, see L<< C<perldoc perlfunc> or C<perldoc -f require> |perlfunc/require >>.
437              
438             =head1 METHODS
439              
440             =head2 new
441              
442             my $object = $class->new(
443             inc => [ 'x', 'y', 'z' , ],
444             immutable => 1 | undef
445             );
446              
447             =head2 immutable
448              
449             if( $inc->immutable ) {
450             print "We're working with a snapshotted version of @INC";
451             }
452              
453             =head2 inc
454              
455             for my $i ( $inc->inc ) {
456             say "Plain: $incer" if not ref $incer;
457             say "Callback: $incer" if ref $incer;
458             }
459              
460             Returns a copy of the internal version of C<@INC> it will be using.
461              
462             If the object is C<immutable>, then this method will continue to report the same value as c<@INC>, or will be updated
463             every time the original array reference passed during construction gets updated:
464              
465             my $ref = [];
466             my $a = Path::ScanINC->new( inc => $ref );
467             my $b = Path::ScanINC->new( inc => $ref, immutable => 1 );
468              
469             push @{$ref} , 'a';
470              
471             is( [ $a->inc ]->[0] , 'a' , "non-immutable references keep tracking their original" );
472             isnt( [ $b->inc ]->[0] , 'a' , "immutable references are shallow-copied at construction" );
473              
474             Do note of course that is a B<SHALLOW> copy, so if you have multiple C<@INC> copies sharing the same C<array>/C<bless>
475             references, changes to those references will be shared amongst all C<@INC>'s .
476              
477             =head2 first_file
478              
479             if( defined ( my $file = $inc->first_file('Moose.pm') ) {
480             print "Yep, Moose seems to be available in \@INC , its at $file, but its not loaded (yet)\n";
481             }
482              
483             This proves to be a handy little gem that replaces the oft used
484              
485             if( try { require Moose ; 1 } ){
486             Yadayadayada
487             }
488              
489             And adds the benefit of not needing to actually source the file to see if it exists or not.
490              
491             =head4 B<IMPORTANT>: PORTABILITIY
492              
493             For best system portability, where possible, its suggested you specify paths as arrays
494             of strings, not slash-separated strings.
495              
496             $inc->first_file('MooseX' , 'Declare.pm') # Good
497             $inc->first_file('MooseX/Declare.pm') # Bad.
498              
499             This is for several reasons, all of which can be summarized as "Windows".
500              
501             =over 4
502              
503             =item * C<%INC> keys all use Unix notation.
504              
505             =item * C<@INC> callbacks expect Unix notation.
506              
507             =item * C<\> is a valid path part on Unix.
508              
509             =item * On Win32, we have to use C<\> Separation, not C</> for resolving physical files.
510              
511             =back
512              
513             The sum of these means if you do this:
514              
515             $inc->first_file('MooseX/Declare.pm')
516              
517             On win32, it might just end up doing:
518              
519             C:\some\path\here/MooseX/Declare.pm
520              
521             Which may or may not work.
522              
523             And additionally, if the above module is loaded, it will be loaded as
524              
525             "MooseX/Declare.pm"
526              
527             in C<%INC>, not what you'd expect, C<MooseX\Declare.pm>
528              
529             =head2 all_files
530              
531             Returns all matches in all C<@INC> paths.
532              
533             my $inc = Path::ScanINC->new();
534             push @INC, 'lib';
535             my ( @files ) = $inc->all_files('Something','Im','Working','On.pm');
536             pp(\@files );
537              
538             # [
539             # '/something/........./lib/Something/Im/Working/On.pm',
540             # '/something/....../share/per5/lib/site_perl/5.15.9/Something/Im/Working/On.pm',
541             # ]
542              
543             Chances are if you understand how this can be useful, you'll do so immediately.
544              
545             Useful for debugging what module is being loaded, and possibly introspecting information about
546             multiple parallel installs of modules in C<%ENV>, such as frequently the case with 'dual-life' modules.
547              
548             perl -MPath::ScanINC -E 'my $scanner = Path::ScanINC->new(); say for $scanner->all_files(qw( Scalar Util.pm ))'
549             /usr/lib64/perl5/vendor_perl/5.12.4/x86_64-linux/Scalar/Util.pm
550             /usr/lib64/perl5/5.12.4/x86_64-linux/Scalar/Util.pm
551              
552             Sort-of like ye' olde' C<perldoc -l>, but more like C<man -a>
553              
554             I might even be tempted to make a sub-module to make one-liners easier like
555              
556             perl -MPath::ScanINC::All=Scalar/Util.pm
557              
558             B<REMINDER>: If there are C<REFS> in C<@INC> that match, they'll return C<array-ref>'s, not strings.
559              
560             =head2 first_dir
561              
562             Just like C<first_file> except for locating directories.
563              
564             =head2 all_dirs
565              
566             Just like C<all_dirs> except for locating directories.
567              
568             =for Pod::Coverage BUILD
569              
570             =head1 AUTHOR
571              
572             Kent Fredric <kentnl@cpan.org>
573              
574             =head1 COPYRIGHT AND LICENSE
575              
576             This software is copyright (c) 2014 by Kent Fredric <kentnl@cpan.org>.
577              
578             This is free software; you can redistribute it and/or modify it under
579             the same terms as the Perl 5 programming language system itself.
580              
581             =cut