File Coverage

blib/lib/Module/Inspector.pm
Criterion Covered Total %
statement 101 111 90.9
branch 29 44 65.9
condition n/a
subroutine 25 25 100.0
pod 10 10 100.0
total 165 190 86.8


line stmt bran cond sub pod time code
1             package Module::Inspector;
2              
3             =pod
4              
5             =head1 NAME
6              
7             Module::Inspector - An integrated API for inspecting Perl distributions
8              
9             =head1 DESCRIPTION
10              
11             An entire ecosystem of CPAN modules exist around the files and formats
12             relating to the CPAN itself. Parsers and object models for various
13             different types of files have been created over the years by various people
14             for various projects.
15              
16             These modules have a variety of different styles, and work in various
17             different ways.
18              
19             So when it comes to analysing the structure of a Perl module (either
20             inside a repository, in a tarball, or in unpacked form) it is certainly
21             quite possible to do.
22              
23             It's just that often it takes a high level of experience with the
24             various modules in question, and the knowledge of how to combine the
25             dozen of so modules in one cohesive program.
26              
27             Personally, I have always found this laborious.
28              
29             What I would prefer is a single API that is easy to use, implements the
30             magic invisibly behind the scenes, and co-ordinates the use of the
31             various modules for me as needed.
32              
33             B provides such an API, and provides a companion to
34             the L API for accessing information on class after
35             installation.
36              
37             It provides a wrapper around the various modules used to read and examine
38             the different parts of a Perl module distribution tarball, and can inspect
39             a module unrolled on disk, in a repository checkout, or just look directly
40             inside a tarball.
41              
42             =head1 METHODS
43              
44             =cut
45              
46 2     2   52973 use 5.005;
  2         8  
  2         84  
47 2     2   12 use strict;
  2         4  
  2         76  
48 2     2   12 use base 'Module::Extract';
  2         12  
  2         2024  
49 2     2   84707 use Carp ();
  2         13  
  2         31  
50 2     2   1573 use version ();
  2         5440  
  2         56  
51 2     2   1852 use Params::Util ('_STRING');
  2         6844  
  2         151  
52 2     2   2182 use File::Find::Rule ();
  2         26300  
  2         59  
53 2     2   2294 use File::Find::Rule::VCS ();
  2         2523  
  2         47  
54 2     2   2060 use File::Find::Rule::Perl ();
  2         8075  
  2         46  
55 2     2   1985 use Module::Manifest ();
  2         3154  
  2         40  
56 2     2   1931 use Module::Math::Depends ();
  2         1567  
  2         43  
57 2     2   2187 use YAML::Tiny ();
  2         13408  
  2         68  
58              
59 2     2   18 use vars qw{$VERSION %SPECIAL};
  2         5  
  2         274  
60             BEGIN {
61 2     2   6 $VERSION = '1.05';
62 2         2243 %SPECIAL = (
63             'MANIFEST' => 'Module::Manifest',
64             'META.yml' => 'YAML::Tiny',
65             );
66             }
67              
68             # If prefork is available, flag PPI for preforking if needed
69 2     2   920 eval " use prefork 'PPI::Document::File'; ";
  0         0  
  0         0  
70              
71              
72              
73              
74              
75             #####################################################################
76             # Constructor
77              
78             =pod
79              
80             =head2 new
81              
82             # Inspect a plain dist directory or cvs/svn checkout
83             my $dir = Module::Inspector->new(
84             dist_dir => $dirpath,
85             );
86            
87             # Inspect a tarball
88             my $file = Module::Inspector->new(
89             dist_file => 'Foo-Bar-0.01.tar.gz',
90             );
91              
92             The C constructor creates a new module inspector. It takes a
93             named param of either C, which should be the file path
94             of the dist tarball, or C, which is the root of the
95             distribution directory (if it is already unrolled).
96              
97             The distribution will be quickly pre-scanned to locate the various
98             significant documents in the distribution (although only a few are
99             initially supported).
100              
101             Returns a new C object, or dies on exception.
102              
103             =cut
104              
105             sub new {
106 1     1 1 1273 my $class = shift;
107 1         16 my $self = $class->SUPER::new(@_);
108              
109             # Auto-detect version control
110 1 50       860658 unless ( defined $self->version_control ) {
111 0         0 $self->{version_control} = $self->_version_control;
112             }
113              
114             # Create the document store
115 1         20 $self->{document} = {};
116              
117             # Add all single special files to the document store
118 1         46 foreach my $file ( sort keys %SPECIAL ) {
119 2 50       12 next unless -f $self->file_path($file);
120 2         189 $self->{document}->{$file} = $SPECIAL{$file};
121             }
122              
123             # Populate the document store with all Perl files
124 1         14 my $find_perl = File::Find::Rule->ignore_vcs($self->version_control)->perl_file;
125 1         2059 foreach my $file ( $find_perl->relative->in($self->dist_dir) ) {
126 13         4496 $self->{document}->{$file} = 'PPI::Document::File';
127             }
128              
129 1         81 $self;
130             }
131              
132             =pod
133              
134             =head2 version_control
135              
136             my $vcs_type = $self->version_control;
137              
138             For reasons that will hopefully be more apparant later,
139             B detects any version control system
140             in use within the C for the module.
141              
142             Currently, support is limited to detection of CVS and
143             Subversion.
144              
145             Returns a the name of the version control system detected in use
146             as a string (currently 'cvs' or 'svn'). If no version control is
147             able to be detected returns the null string ''.
148              
149             =cut
150              
151             sub version_control {
152 3     3 1 2330 my $self = shift;
153              
154             # Determine it if we haven't yet
155 3 100       24 unless ( exists $self->{version_control} ) {
156 1 50       109 if ( -d $self->file_path('.svn') ) {
    50          
157             # We in a subversion checkout
158 0         0 $self->{version_control} = 'svn';
159              
160             } elsif ( -f $self->file_path('CVS/Repository') ) {
161             # We in a CVS checkout
162 0         0 $self->{version_control} = 'cvs';
163              
164             } else {
165             # We have none, or can't tell
166 1         164 $self->{version_control} = '';
167             }
168             }
169              
170 3         55 $self->{version_control};
171             }
172              
173             =head2 documents
174              
175             The C method returns a list of the names of all the documents
176             detected by the C.
177              
178             In scalar context, returns the number of identifyable documens found in
179             the distribution.
180              
181             =cut
182              
183             sub documents {
184 1 50   1 1 5 if ( wantarray ) {
185 1         2 return sort keys %{ $_[0]->{document} };
  1         16  
186             } else {
187 0         0 return scalar keys %{ $_[0]->{document} };
  0         0  
188             }
189             }
190              
191             =pod
192              
193             =head2 document_type
194              
195             # Returns 'PPI::Document::File'
196             my $ppi_class = $inspector->document_type('lib/Foo.pm');
197              
198             In B, all documents are represented as objects.
199              
200             Thus, for each different type of document, there is going to be a
201             different class that implements the document objects for that type.
202              
203             The C method returns the type for a provided document
204             as a class name.
205              
206             Please note that at this time these document types are not necesarily
207             stable, and over the first several releases I may need to change
208             the class I'm using to represent a particular document type.
209              
210             =cut
211              
212             sub document_type {
213 6     6 1 2080 my $self = shift;
214 6 50       34 my $file = _STRING(shift)
215             or Carp::croak("Missing or invalid param to document_type");
216 6 50       21 unless ( defined $self->{document}->{$file} ) {
217 0         0 Carp::croak("Document $file does not exist in module");
218             }
219 6 100       83 ref($self->{document}->{$file}) or $self->{document}->{$file};
220             }
221              
222             =pod
223              
224             =head2 document
225              
226             my $perl = $inspector->document('lib/Foo.pm');
227              
228             The C method returns the document object for a named file,
229             loading and caching it on the fly if needed.
230              
231             The type of object will vary depending on the document.
232              
233             For example, a Perl file will be returned as a L,
234             a F file as a L, and so on.
235              
236             Returns an object, or dies on error.
237              
238             =cut
239              
240             sub document {
241 17     17 1 35 my $self = shift;
242 17 50       77 my $file = _STRING(shift)
243             or Carp::croak("Missing or invalid param to document_type");
244 17 50       78 unless ( defined $self->{document}->{$file} ) {
245 0         0 Carp::croak("Document $file does not exist in module");
246             }
247              
248             # Return the document if loaded
249 17 100       56 if ( ref $self->{document}->{$file} ) {
250 14         50 return $self->{document}->{$file};
251             }
252              
253             # Load the document
254 3         22 my $path = $self->file_path($file);
255 3         73 my $loader = $self->{document}->{$file};
256 3 100       18 if ( $loader eq 'PPI::Document::File' ) {
    100          
    50          
257 1         1005 require PPI::Document::File;
258 1 50       2543808 my $document = PPI::Document::File->new( $path )
259             or Carp::croak("Failed to load $file with PPI::Document::File");
260 1         20877 $self->{document}->{$file} = $document;
261              
262             } elsif ( $loader eq 'YAML::Tiny' ) {
263 1 50       23 my $document = YAML::Tiny->read( $path )
264             or Carp::croak("Failed to load $file with $loader");
265 1         46785 $self->{document}->{$file} = $document;
266              
267             } elsif ( $loader eq 'Module::Manifest' ) {
268 1 50       30 my $document = Module::Manifest->new( $path )
269             or Carp::croak("Failed to load $file with $loader");
270 1         355 $self->{document}->{$file} = $document;
271              
272             } else {
273 0         0 die "Internal Error: Unknown document loader '$loader'";
274             }
275              
276 3         27 $self->{document}->{$file};
277             }
278              
279              
280              
281              
282              
283             #####################################################################
284             # Analysis Layer
285              
286             =pod
287              
288             =head2 dist_name
289              
290             # Returns Config-Tiny
291             my $name = $inspector->dist_name;
292              
293             The C method returns the name of the distribution, as
294             determined from the META.yml file.
295              
296             Returns the name as a string, or dies on error.
297              
298             =cut
299              
300             sub dist_name {
301 1     1 1 802 my $self = shift;
302 1         4 my $meta = $self->document('META.yml');
303 1 50       8 $meta->[0]->{name} or Carp::croak(
304             "META.yml does not have a name: value"
305             );
306             }
307              
308             =pod
309              
310             =head2 dist_version
311              
312             The C method returns the version of the distribution, as
313             determined from the F file in the distribution.
314              
315             Returns a L object, or dies on error.
316              
317             =cut
318              
319             sub dist_version {
320 1     1 1 3 my $self = shift;
321 1         3 my $meta = $self->document('META.yml');
322 1 50       7 my $version = $meta->[0]->{version}
323             or Carp::croak("META.yml does not have a version: value");
324 1         17 version->new($version);
325             }
326              
327             =pod
328              
329             =head2 dist_requires
330              
331             my $depends = $inspector->dist_requires;
332              
333             The C method checks for any run-time dependencies of the
334             distribution and returns them as a L object.
335              
336             See the docs for L for more information on its
337             structure and API.
338              
339             If the distribution has no run-time dependencies, the object will still
340             be returned, but will be empty.
341              
342             Returns a single L object, or dies on error.
343              
344             =cut
345              
346             sub dist_requires {
347 4     4 1 5571 my $self = shift;
348 4         12 my $meta = $self->document('META.yml');
349 4         10 my $requires = $meta->[0]->{requires};
350 4 100       37 return $requires
351             ? Module::Math::Depends->from_hash( $requires )
352             : Module::Math::Depends->new;
353             }
354              
355             =pod
356              
357             =head2 dist_build_requires
358              
359             The C method returns the build-time-only
360             dependencies of the distribution.
361              
362             If there are no build-time dependencies, the object will still
363             be returned, but will be empty.
364              
365             Returns a L object, or dies on exception.
366              
367             =cut
368              
369             sub dist_build_requires {
370 4     4 1 1380 my $self = shift;
371 4         9 my $meta = $self->document('META.yml');
372 4         7 my $requires = $meta->[0]->{build_requires};
373 4 100       18 return $requires
374             ? Module::Math::Depends->from_hash( $requires )
375             : Module::Math::Depends->new;
376             }
377              
378             =pod
379              
380             =head2 dist_depends
381              
382             The C method returns as for the two methods above
383             (C and C) except that this
384             method returns a merged dependency object, representing BOTH the
385             install-time and run-time dependencies for the distribution.
386              
387             If there are no build-time or run-time dependencies, the object
388             will be returned, but will be empty.
389              
390             Returns a L object, or dies on error.
391              
392             =cut
393              
394             sub dist_depends {
395 2     2 1 1186 my $self = shift;
396 2         6 my $requires = $self->dist_requires;
397 2         156 my $build_requires = $self->dist_build_requires;
398 2         100 $requires->merge( $build_requires );
399 2         44 return $requires;
400             }
401              
402             1;
403              
404             =pod
405              
406             =head1 TO DO
407              
408             - Implement most of the functionality
409              
410             =head1 SUPPORT
411              
412             This module is stored in an Open Repository at the following address.
413              
414             L
415              
416             Write access to the repository is made available automatically to any
417             published CPAN author, and to most other volunteers on request.
418              
419             If you are able to submit your bug report in the form of new (failing)
420             unit tests, or can apply your fix directly instead of submitting a patch,
421             you are B encouraged to do so as the author currently maintains
422             over 100 modules and it can take some time to deal with non-Critcal bug
423             reports or patches.
424              
425             This will guarentee that your issue will be addressed in the next
426             release of the module.
427              
428             If you cannot provide a direct test or fix, or don't have time to do so,
429             then regular bug reports are still accepted and appreciated via the CPAN
430             bug tracker.
431              
432             L
433              
434             For other issues, for commercial enhancement or support, or to have your
435             write access enabled for the repository, contact the author at the email
436             address above.
437              
438             =head1 AUTHOR
439              
440             Adam Kennedy Eadamk@cpan.orgE
441              
442             =head1 SEE ALSO
443              
444             L
445              
446             =head1 COPYRIGHT
447              
448             Copyright 2006 - 2008 Adam Kennedy.
449              
450             This program is free software; you can redistribute
451             it and/or modify it under the same terms as Perl itself.
452              
453             The full text of the license can be found in the
454             LICENSE file included with this module.
455              
456             =cut