File Coverage

lib/Module/Data.pm
Criterion Covered Total %
statement 52 56 92.8
branch 9 10 90.0
condition n/a
subroutine 16 17 94.1
pod 3 3 100.0
total 80 86 93.0


line stmt bran cond sub pod time code
1 4     4   97825 use strict;
  4         10  
  4         301  
2 4     4   29 use warnings;
  4         8  
  4         247  
3              
4             package Module::Data;
5             BEGIN {
6 4     4   138 $Module::Data::AUTHORITY = 'cpan:KENTNL';
7             }
8             {
9             $Module::Data::VERSION = '0.007';
10             }
11              
12             # ABSTRACT: Introspect context information about modules in @INC
13 4     4   6671 use Moo;
  4         129089  
  4         35  
14 4     4   19881 use Sub::Quote;
  4         18830  
  4         4391  
15              
16             around BUILDARGS => sub {
17             my ( $orig, $class, @args ) = @_;
18              
19             unshift @args, 'package' if @args % 2 == 1;
20              
21             return $class->$orig(@args);
22             };
23              
24              
25              
26             has package => (
27             required => 1,
28             is => 'ro',
29             isa => quote_sub q{}
30             . q{die "given undef for 'package' , expects a Str/module name" if not defined $_[0];}
31             . q{die " ( 'package' => $_[0] ) is not a Str/module name, got a ref : " . ref $_[0] if ref $_[0];}
32             . q{require Module::Runtime;}
33             . q{Module::Runtime::check_module_name( $_[0] );},
34             );
35              
36             has _notional_name => (
37             is => 'ro',
38             lazy => 1,
39             default => quote_sub q{} . q{require Module::Runtime;} . q{return Module::Runtime::module_notional_filename( $_[0]->package );},
40             );
41              
42              
43             sub loaded {
44 15     15 1 35 my ($self) = @_;
45 15         435 return exists $INC{ $self->_notional_name };
46             }
47              
48              
49             ## no critic ( ProhibitBuiltinHomonyms )
50             sub require {
51 4     4 1 9 my ($self) = @_;
52 4 50       29 return $self->package if $self->loaded;
53              
54 4         214 require Module::Runtime;
55 4         23 Module::Runtime::require_module( $self->package );
56 4         4411 return $self->package;
57             }
58              
59             sub _find_module_perl {
60 0     0   0 my ($self) = @_;
61 0         0 $self->require;
62 0         0 return $INC{ $self->_notional_name };
63             }
64              
65             sub _find_module_emulate {
66 4     4   9 my ($self) = @_;
67 4         76 require Path::ScanINC;
68 4         32 return Path::ScanINC->new()->first_file( $self->_notional_name );
69             }
70              
71             sub _find_module_optimistic {
72 5     5   13 my ($self) = @_;
73 5 100       21 return $INC{ $self->_notional_name } if $self->loaded;
74 4         690 return $self->_find_module_emulate;
75             }
76              
77             ## use critic
78              
79              
80             has path => (
81             is => 'ro',
82             lazy => 1,
83             init_arg => undef,
84             builder => '_build_path',
85             );
86              
87             sub _build_path {
88 5     5   7715 require Path::Class::File;
89 5         57069 return Path::Class::File->new( $_[0]->_find_module_optimistic )->absolute;
90             }
91              
92              
93             has root => (
94             is => 'ro',
95             lazy => 1,
96             init_arg => undef,
97             builder => '_build_root',
98             );
99              
100             sub _build_root {
101 5     5   10725 my ($path) = $_[0]->path;
102              
103             # Parent ne Self is the only cross-platform way
104             # I can think of that will stop at the top of a tree
105             # as / is not applicable on windows.
106 5         1177 while ( $path->parent->absolute ne $path->absolute ) {
107 15 100       6988 if ( not $path->is_dir ) {
108 5         34 $path = $path->parent;
109 5         49 next;
110             }
111 10 100       431 if ( $path->file( $_[0]->_notional_name )->absolute eq $_[0]->path->absolute ) {
112 5         1707 return $path->absolute;
113             }
114 5         1683 $path = $path->parent;
115             }
116 0         0 return;
117              
118             }
119              
120              
121             sub _version_perl {
122 4     4   12511 my ($self) = @_;
123 4         60 $self->require;
124              
125             # has to load the code into memory to work
126 4         129 return $self->package->VERSION;
127             }
128              
129             sub _version_emulate {
130 4     4   9 my ($self) = @_;
131 4         106 my $path = $self->path;
132 4         61 require Module::Metadata;
133 4         34 my $i = Module::Metadata->new_from_file( $path, collect_pod => 0 );
134 4         18536 return $i->version( $self->package );
135             }
136              
137             sub _version_optimistic {
138 6     6   10 my ($self) = @_;
139 6 100       20 return $self->package->VERSION if $self->loaded;
140 4         174 return $self->_version_emulate;
141             }
142              
143             sub version {
144 6     6 1 5478 my ( $self, @junk ) = @_;
145 6         22 return $self->_version_optimistic;
146             }
147              
148              
149 4     4   33 no Moo;
  4         13  
  4         35  
150              
151             1;
152              
153             __END__
154              
155             =pod
156              
157             =encoding UTF-8
158              
159             =head1 NAME
160              
161             Module::Data - Introspect context information about modules in @INC
162              
163             =head1 VERSION
164              
165             version 0.007
166              
167             =head1 SYNOPSIS
168              
169             use Module::Data;
170              
171             my $d = Module::Data->new( 'Package::Stash' );
172              
173             $d->path; # returns the path to where Package::Stash was found in @INC
174              
175             $d->root; # returns the root directory in @INC that 'Package::Stash' was found inside.
176              
177             # Convenient trick to discern if you're in a development environment
178              
179             my $d = Module::Data->new( 'Module::Im::Developing' );
180              
181             if ( -e $d->root->parent->subdir('share') ) {
182             # Yep, this dir exists, so we're in a dev context.
183             # because we know in the development context all modules are in lib/*/*
184             # so if the modules are anywhere else, its not a dev context.
185             # see File::ShareDir::ProjectDistDir for more.
186             }
187              
188             # Helpful sugar.
189              
190             my $v = $d->version;
191              
192             =head1 METHODS
193              
194             =head2 package
195              
196             Returns the package the C<Module::Data> instance was created for. ( In essence,
197             this will just return the value you passed during C<new>, nothing more, nothing
198             less.
199              
200             my $package = $md->package
201              
202             =head2 loaded
203              
204             Check to see if the module is already recorded as being loaded in C<%INC>
205              
206             if ( $md->loaded ) {
207             say "$md was loaded";
208             }
209              
210             =head2 require
211              
212             Require the module be loaded into memory and the global stash.
213              
214             my $mod = Module::Data->new( 'Foo' ); # nothing much happens.
215             $mod->require; # like 'require Foo';
216              
217             Returns the L</package> name itself for convenience so you can do
218              
219             my $mod = Module::Data->new('Foo');
220             $mod->require->new( %args );
221              
222             =head2 path
223              
224             A Path::Class::File with the absolute path to the found module.
225              
226             my $md = Module::Data->new( 'Foo' );
227             my $path = $md->path;
228              
229             C<$path> is computed optimistically. If the L</package> is listed as being
230             L</loaded>, then it asks C<%INC> for where it was found, otherwise, the path is
231             resolved by simulating C<perl>'s path look up in C<@INC> via
232             L<< C<Path::ScanINC>|Path::ScanINC >>.
233              
234             =head2 root
235              
236             Returns the base directory of the tree the module was found at.
237             ( Probably from @INC );
238              
239             local @INC = (
240             "somewhere/asinine/",
241             "somewhere/in/space/", # Where Lib::Foo::Bar is
242             "somethingelse/",
243             );
244             my $md = Module::Data->new( "Lib::Foo::Bar");
245             $md->path ; # somewhere/in/space/Lib/Foo/Bar.pm
246             my $root = $md->root # somewhere/in/space
247              
248             =head2 version
249              
250             If the module appears to be already loaded in memory:
251              
252             my $v = $md->version;
253              
254             is merely shorthand for $package->VERSION;
255              
256             However, if if the module is not loaded into memory, all efforts to extract the
257             value without loading the code permanently are performed.
258              
259             Here, this means we compute the path to the file manually ( see L</path> ) and
260             parse the file with L<< C<Module::Metadata>|Module::Metadata >> to statically extract C<$VERSION>.
261              
262             This means you can unleash this code on your entire installed module tree, while
263             incurring no permanent memory gain as you would normally incur if you were to
264             C<require> them all.
265              
266             =for Pod::Coverage BUILDARGS
267              
268             =head1 AUTHOR
269              
270             Kent Fredric <kentnl@cpan.org>
271              
272             =head1 COPYRIGHT AND LICENSE
273              
274             This software is copyright (c) 2013 by Kent Fredric <kentnl@cpan.org>.
275              
276             This is free software; you can redistribute it and/or modify it under
277             the same terms as the Perl 5 programming language system itself.
278              
279             =cut