File Coverage

blib/lib/Module/Manifest.pm
Criterion Covered Total %
statement 82 104 78.8
branch 43 64 67.1
condition 7 15 46.6
subroutine 17 18 94.4
pod 9 9 100.0
total 158 210 75.2


line stmt bran cond sub pod time code
1             package Module::Manifest;
2              
3             =pod
4              
5             =head1 NAME
6              
7             Module::Manifest - Parse and examine a Perl distribution MANIFEST file
8              
9             =head1 SYNOPSIS
10              
11             Open and parse a MANIFEST and MANIFEST.SKIP:
12              
13             my $manifest = Module::Manifest->new( 'MANIFEST', 'MANIFEST.SKIP' );
14              
15             Check if a given file matches any known skip masks:
16              
17             print "yes\n" if $manifest->skipped('.svn');
18              
19             =head1 DESCRIPTION
20              
21             B is a simple utility module created originally for use
22             in L.
23              
24             It can load a F file that comes in a Perl distribution tarball,
25             examine the contents, and perform some simple tasks. It can also load the
26             F file and check that.
27              
28             Granted, the functionality needed to do this is quite simple, but the
29             Perl distribution F specification contains a couple of little
30             idiosyncracies, such as line comments and space-seperated inline
31             comments.
32              
33             The use of this module means that any little nigglies are dealt with behind
34             the scenes, and you can concentrate the main task at hand.
35              
36             =head2 Comparison to ExtUtil::Manifest
37              
38             This module is quite similar to L, or is at least
39             similar in scope. However, there is a general difference in approach.
40              
41             L is imperative, requires the existance of the actual
42             F file on disk, and requires that your current directory remains
43             the same.
44              
45             L treats the F file as an object, can load
46             a the file from anywhere on disk, and can run some of the same
47             functionality without having to change your current directory context.
48              
49             That said, note that L is aimed at reading and checking
50             existing MANFIFEST files, rather than creating new ones.
51              
52             =head1 COMPATIBILITY
53              
54             This module should be compatible with Perl 5.005 and above. However, it has
55             only been rigorously tested under Perl 5.10.0 on Linux.
56              
57             If you encounter any problems on a different version or architecture, please
58             contact the maintainer.
59              
60             =head1 METHODS
61              
62             =cut
63              
64 3     3   60708 use 5.00503;
  3         10  
  3         115  
65 3     3   18 use strict;
  3         5  
  3         84  
66 3     3   23 use Carp ();
  3         5  
  3         46  
67 3     3   22 use File::Spec ();
  3         4  
  3         50  
68 3     3   13 use File::Spec::Unix ();
  3         5  
  3         57  
69 3     3   20 use File::Basename ();
  3         6  
  3         41  
70 3     3   2870 use Params::Util ();
  3         17079  
  3         81  
71              
72 3     3   23 use vars qw{$VERSION};
  3         6  
  3         143  
73             BEGIN {
74 3     3   9888 $VERSION = '1.08';
75             }
76              
77             # These platforms were copied from File::Spec
78             my %platforms = (
79             MacOS => 1,
80             MSWin32 => 1,
81             os2 => 1,
82             VMS => 1,
83             epoc => 1,
84             NetWare => 1,
85             symbian => 1,
86             dos => 1,
87             cygwin => 1,
88             );
89              
90             =pod
91              
92             =head2 new
93              
94             Module::Manifest->new( $manifest, $skip )
95              
96             Creates a C object, which either parses the files
97             referenced by the C<$manifest> (for MANIFEST) and C<$skip>
98             (for MANIFEST.SKIP). If no parameters are specified, it creates an empty
99             object.
100              
101             Example code:
102              
103             my $manifest = Module::Manifest->new;
104             my $manifest = Module::Manifest->new( $manifest );
105             my $manifest = Module::Manifest->new( $manifest, $skip );
106              
107             This method will return an appropriate B object or
108             throws an exception on error.
109              
110             =cut
111              
112             sub new {
113 15     15 1 10309 my ($class, $manifest, $skipfile) = @_;
114              
115 15 100       46 Carp::carp('Return value discarded') unless (defined wantarray);
116              
117 15         486 my $self = bless {
118             file => $manifest,
119             skipfile => $skipfile,
120             }, $class;
121              
122 15 50       56 $self->open( skip => $skipfile ) if Params::Util::_STRING($skipfile);
123 15 50       41 $self->open( manifest => $manifest ) if Params::Util::_STRING($manifest);
124              
125 15         38 return $self;
126             }
127              
128             =pod
129              
130             =head2 open
131              
132             $manifest->open( $type => $filename )
133              
134             Open and parse the file given by C<$filename>, which may be a relative path.
135             The available C<$type> options are either: 'skip' or 'manifest'
136              
137             Example code:
138              
139             $manifest->open( skip => 'MANIFEST.SKIP' );
140             $manifest->open( manifest => 'MANIFEST' );
141              
142             This method doesn't return anything, but may throw an exception on error.
143              
144             =cut
145              
146             sub open {
147 4     4 1 862 my ($self, $type, $name) = @_;
148              
149 4 100       20 Carp::croak('You must call this method as an object') unless (ref $self);
150 3 100 100     15 unless ( defined $name && length $name ) {
151 2         30 Carp::croak('You must pass a filename to read and parse');
152             }
153              
154             # Derelativise the file name if needed
155 1         48 my $file = File::Spec->rel2abs($name);
156 1         64 $self->{dir} = File::Basename::dirname($file);
157              
158 1 50 33     33 unless ( -f $file and -r _ ) {
159 1         15 Carp::croak('Did not provide a readable file path');
160             }
161              
162 0         0 my @file;
163 0 0       0 unless ( open(MANFILE, $file) ) {
164 0         0 Carp::croak('Failed to load ' . $name . ': ' . $!);
165             }
166             # Slurping should be fine since files are relatively small
167 0         0 @file = ;
168 0 0       0 unless ( close MANFILE ) {
169 0         0 Carp::croak('Failed to close file! This is VERY bad.');
170             }
171              
172             # Parse the file
173 0         0 $self->parse( $type => \@file );
174              
175 0         0 return;
176             }
177              
178             =pod
179              
180             =head2 parse
181              
182             $manifest->parse( $type => \@files )
183              
184             Parse C<\@files>, which is an array reference containing a list of files or
185             regular expression masks. The available C<$type> options are either: 'skip'
186             or 'manifest'
187              
188             Example code:
189              
190             $manifest->parse( skip => [
191             '\B\.svn\b',
192             '^Build$',
193             '\bMakefile$',
194             ]);
195              
196             This method doesn't return anything, but may throw an exception on error.
197              
198             =cut
199              
200             sub parse {
201 5     5 1 828 my ($self, $type, $array) = @_;
202              
203 5 100       23 Carp::croak('You must call this method as an object') unless (ref $self);
204 4 100       28 unless ( ref $array eq 'ARRAY' ) {
205 1         8 Carp::croak('Files or masks must be specified as an array reference');
206             }
207              
208             # This hash ensures there are no duplicates
209 3         5 my %hash;
210 3         5 foreach my $line (@{$array}) {
  3         9  
211 8 50       40 next unless $line =~ /^\s*([^\s#]\S*)/;
212 8 100       36 if ($hash{$1}++) {
213 1         14 Carp::carp('Duplicate file or mask ' . $1);
214             }
215             }
216              
217 3         573 my @masks = sort keys %hash;
218 3 100       18 if ($type eq 'skip') {
    100          
219 1         9 $self->{skiplist} = \@masks;
220             } elsif ($type eq 'manifest') {
221 1         4 $self->{manifest} = \@masks;
222             } else {
223 1         10 Carp::croak('Available types are: skip, manifest');
224             }
225              
226 2         16 return;
227             }
228              
229             =pod
230              
231             =head2 skipped
232              
233             $manifest->skipped( $filename )
234              
235             Check if C<$filename> matches any masks that should be skipped, given the
236             regular expressions provided to either the C or C methods.
237              
238             Absolute path names must first be relativized and converted to a Unix-like
239             path string by using the C method.
240              
241             Example code:
242              
243             if ($manifest->skipped('Makefile.PL')) {
244             # do stuff
245             }
246              
247             This method returns a boolean true or false value indicating whether the
248             file path is skipped according the C.
249              
250             =cut
251              
252             sub skipped {
253 10     10 1 759 my ($self, $file) = @_;
254              
255 10 100       37 Carp::croak('You must call this method as an object') unless (ref $self);
256 9 100       30 Carp::carp('Return value discarded') unless (defined wantarray);
257 9 100 100     459 unless ( defined $file && length $file ) {
258 2         19 Carp::croak('You must pass a filename or path to check');
259             }
260              
261             # Quit early if we have no skip list
262 7 100       592 return 0 unless (exists $self->{skiplist});
263              
264             # Loop through masks and exit early if there's a match
265 6         8 foreach my $mask (@{ $self->{skiplist} }) {
  6         14  
266 14 100       199 return 1 if ($file =~ /$mask/i);
267             }
268 2         10 return 0;
269             }
270              
271             =pod
272              
273             =head2 normalize
274              
275             Module::Manifest->normalize( $path, $rel )
276             $manifest->normalize( $path, $rel )
277              
278             This method takes a given platform-specific path string and converts it
279             to a Unix-style string compatible with the MANIFEST and MANIFEST.SKIP
280             specifications.
281              
282             Note that this method normalizes paths depending on the platform detected
283             by C<$^O> -- that is, Win32 style paths can only be normalized if the
284             module is currently running under Win32.
285              
286             By default, this method will relativize file paths to the current working
287             directory (using L's C method without a C<$root>). To
288             disable this behaviour, set C<$rel> to a false value.
289              
290             Example code:
291              
292             # Useful for normalizing Win32-style paths
293             my $normal = Module::Manifest->normalize('t\\test\\file');
294             # Returns: t/test/file (ie, in Unix style for MANIFEST)
295              
296             This returns a normalized version of the given path.
297              
298             =cut
299              
300             sub normalize {
301 0     0 1 0 my (undef, $path, $rel) = @_;
302              
303 0 0       0 Carp::carp('Return value discarded') unless (defined wantarray);
304 0 0 0     0 unless ( defined $path && length $path ) {
305 0         0 Carp::croak('You must pass a filename or path to check');
306             }
307              
308             # Relativize if $rel is undefined or a true value
309 0 0 0     0 if ( !defined $rel || $path ) {
310 0         0 $path = File::Spec->abs2rel($path);
311             }
312              
313             # Portably deal with different OSes
314 0 0       0 if ($platforms{$^O}) { # Check if we are on a non-Unix platform
315             # Get path info from File::Spec, split apart
316 0         0 my (undef, $dir, $filename) = File::Spec->splitpath($path);
317 0         0 my @dir = File::Spec->splitdir($dir);
318              
319             # Reconstruct the path in Unix-style
320 0         0 $dir = File::Spec::Unix->catdir(@dir);
321 0         0 $path = File::Spec::Unix->catpath(undef, $dir, $filename);
322             }
323              
324 0         0 return $path;
325             }
326              
327             =pod
328              
329             =head2 file
330              
331             $manifest->file
332              
333             The C accessor returns the absolute path of the MANIFEST file that
334             was loaded.
335              
336             =cut
337              
338             sub file {
339 2     2 1 741 my ($self) = @_;
340 2 100       17 Carp::croak('You must call this method as an object') unless (ref $self);
341 1 50       12 Carp::carp('Return value discarded') unless (defined wantarray);
342 1         415 return $self->{file};
343             }
344              
345             =pod
346              
347             =head2 skipfile
348              
349             $manifest->skipfile
350              
351             The C accessor returns the absolute path of the MANIFEST.SKIP file
352             that was loaded.
353              
354             =cut
355              
356             sub skipfile {
357 2     2 1 742 my ($self) = @_;
358 2 100       14 Carp::croak('You must call this method as an object') unless (ref $self);
359 1 50       11 Carp::carp('Return value discarded') unless (defined wantarray);
360 1         466 return $self->{skipfile};
361             }
362              
363             =pod
364              
365             =head2 dir
366              
367             $manifest->dir
368              
369             The C accessor returns the path to the directory that contains the
370             MANIFEST or skip file, and thus SHOULD be the root of the distribution.
371              
372             =cut
373              
374             sub dir {
375 2     2 1 763 my ($self) = @_;
376 2 100       14 Carp::croak('You must call this method as an object') unless (ref $self);
377 1 50       10 Carp::carp('Return value discarded') unless (defined wantarray);
378 1         430 return $self->{dir};
379             }
380              
381             =pod
382              
383             =head2 files
384              
385             $manifest->files
386              
387             The C method returns the (relative, unix-style) list of files within
388             the manifest. In scalar context, returns the number of files in the manifest.
389              
390             Example code:
391              
392             my @files = $manifest->files;
393              
394             =cut
395              
396             sub files {
397 2     2 1 855 my ($self) = @_;
398 2 100       19 Carp::croak('You must call this method as an object') unless (ref $self);
399 1 50       10 Carp::carp('Return value discarded') unless (defined wantarray);
400 1 50       527 if (exists($self->{manifest})) {
401 0         0 return @{ $self->{manifest} };
  0         0  
402             }
403 1         6 return ();
404             }
405              
406             1;
407              
408             =pod
409              
410             =head1 LIMITATIONS
411              
412             The directory returned by the C method is overwritten whenever C
413             is called. This means that, if MANIFEST and MANIFEST.SKIP are not in the
414             same directory, the module may get a bit confused.
415              
416             =head1 SUPPORT
417              
418             This module is stored in an Open Repository at the following address:
419              
420             L
421              
422             Write access to the repository is made available automatically to any
423             published CPAN author, and to most other volunteers on request.
424              
425             If you are able to submit your bug report in the form of new (failing) unit
426             tests, or can apply your fix directly instead of submitting a patch, you are
427             B encouraged to do so. The author currently maintains over 100
428             modules and it may take some time to deal with non-critical bug reports or
429             patches.
430              
431             This will guarantee that your issue will be addressed in the next release of
432             the module.
433              
434             If you cannot provide a direct test or fix, or don't have time to do so, then
435             regular bug reports are still accepted and appreciated via the CPAN bug
436             tracker.
437              
438             L
439              
440             For other issues, for commercial enhancement and support, or to have your
441             write access enabled for the repository, contact the author at the email
442             address above.
443              
444             =head1 AUTHOR
445              
446             Adam Kennedy Eadamk@cpan.orgE
447              
448             =head2 CONTIRBUTORS
449              
450             Jonathan Yu Ejawnsy@cpan.orgE
451              
452             =head1 SEE ALSO
453              
454             L
455              
456             =head1 COPYRIGHT
457              
458             Copyright 2006 - 2010 Adam Kennedy
459              
460             This program is free software; you can redistribute it and/or modify it
461             under the same terms as Perl itself.
462              
463             The full text of the license can be found in the LICENSE file included
464             with this module.
465              
466             =cut