File Coverage

script/pmcheck
Criterion Covered Total %
statement 57 64 89.0
branch 11 28 39.2
condition 2 12 16.6
subroutine 15 15 100.0
pod n/a
total 85 119 71.4


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             =encoding utf8
4              
5             =head1 NAME
6              
7             pmcheck - show all Perl packages provided or required
8              
9             =head1 SYNOPSIS
10              
11             pmcheck [ --missing | --provided ] [ ]
12              
13             =head1 DESCRIPTION
14              
15             Recursively extract package information from all readable non-empty regular
16             text files in the source tree. The current directory is used if no directory
17             or file is specified. All dot-files and directories are ignored.
18              
19             If the --provided option is given, shows all packages provided by .pm files
20             in the source tree. Otherwise shows all packages required by .cgi, .pl, .pm
21             and .t files that are not provided anywhere in the source tree (and also
22             nowhere in @INC if the --missing option is given). Files with no dot in the
23             name are also checked if they start with a perl shebang line.
24              
25             NOTE: Packages required by the 'use base' and 'use parent' pragmas and by the
26             Moose 'extends' and 'with' functions will only be included if Module::Used is
27             available. Will display an activity indicator if run interactively and
28             Term::Activity is available.
29              
30             =head1 OPTIONS
31              
32             --missing Show all required packages that are not available in @INC
33             --provided Show all packages provided by .pm files
34              
35             =head1 EXAMPLES
36              
37             pmcheck
38              
39             pmcheck -m
40              
41             pmcheck -p
42              
43             =head1 COPYRIGHT AND LICENSE
44              
45             Created by Andrew Pam
46             Copyright 2011-2012 Strategic Data
47              
48             This program is free software; you may redistribute it and/or modify it under
49             the same terms as Perl itself.
50              
51             =cut
52              
53 1     1   717 use 5.010;
  1         4  
  1         41  
54 1     1   5 use strict;
  1         2  
  1         35  
55 1     1   5 use utf8;
  1         1  
  1         7  
56 1     1   20 use warnings;
  1         2  
  1         44  
57              
58 1     1   1244 use Fatal qw( open close );
  1         16723  
  1         5  
59 1     1   956 use File::Find qw( find );
  1         2  
  1         84  
60 1     1   1262 use Getopt::Long 2.33 qw( GetOptions );
  1         10699  
  1         27  
61 1     1   3382 use Module::CoreList;
  1         44450  
  1         14  
62 1     1   3629 use Pod::Usage qw( pod2usage );
  1         69582  
  1         112  
63 1     1   1159 use PPI::Document;
  1         170304  
  1         40  
64              
65 1     1   13 use version; our $VERSION = version->new('v1.1.11');
  1         2  
  1         10  
66              
67             ########################################
68             # Constants
69              
70             my $CORELIST = $Module::CoreList::version{$]};
71             my $MODULE_USED = eval { require Module::Used };
72             my $SHOW_ACTIVITY
73             = -t *STDERR && eval { require Term::Activity }; ## no critic (ProhibitInteractiveTest)
74              
75             ########################################
76             # Global variables
77              
78             my $activity;
79              
80             our ( %opt, %provided, %required ); ## no critic (ProhibitPackageVars)
81              
82             ########################################
83             # Subroutines
84              
85             sub first_line {
86 1     1   459 open( my $fh, '<', shift );
87 1         96 my $line = <$fh>;
88 1         26 close $fh;
89              
90 1         33 return $line;
91             } ## end sub first_line
92              
93             sub modules_used {
94 3     3   9645 my $doc = PPI::Document->new( shift, readonly => 1 );
95              
96             ## no critic (Modules::RequireExplicitInclusion)
97 3 50       47992 return Module::Used::modules_used_in_document($doc) if $MODULE_USED;
98              
99             # Doesn't handle 'base' and 'parent' pragmas or Moose 'extends' and 'with'
100 0 0       0 my $includes = $doc->find('PPI::Statement::Include') or return;
101 0         0 return map { $_->module } grep { $_->module } @$includes;
  0         0  
  0         0  
102             } ## end sub modules_used
103              
104             sub provided {
105 1 50   1   3664 return if $File::Find::name =~ m{ / \. [^.] }x; # Ignore hidden
106 1 50       8 return if !m/ \.pm \z /x; # Only check .pm files
107 1 50 33     152 return if !( -f -r ) || -z _ || -B _; ## no critic (ProhibitFiletest_f)
      33        
108              
109 1 50       5 $activity->tick if $SHOW_ACTIVITY;
110 1         9 my $doc = PPI::Document->new( $_, readonly => 1 );
111 1 50       5491 my $packages = eval { $doc->find('PPI::Statement::Package') } or return;
  1         7  
112 1         1693 $provided{ $_->schild(1)->content }++ foreach @$packages;
113              
114 1         24 return;
115             } ## end sub provided
116              
117             sub required {
118 1 50   1   1328 return if $File::Find::name =~ m{ / \. [^.] }x; # Ignore hidden
119              
120 1 50       36 if (m/ \. /x) {
121 1 50       9 return if !m/ \. (?: cgi | p[lm] | t ) \z /x; # Check file extension
122             }
123             else {
124 0 0 0     0 return if !( -f -r ) || -z _ || -B _; ## no critic (ProhibitFiletest_f)
      0        
125             ## Ignore files with no dot that don't start with a perl shebang line
126 0 0       0 return if !eval { first_line($_) =~ m{ \A \#! .* /perl }x };
  0         0  
127             }
128              
129 1 50       5 $activity->tick if $SHOW_ACTIVITY;
130 1 50       3 my @packages = eval { modules_used($_) } or return;
  1         4  
131 1         16079 $required{$_}++ foreach grep { !exists $CORELIST->{$_} } @packages;
  7         1516  
132              
133 1         73 return;
134             } ## end sub required
135              
136             ########################################
137             # Mainline code
138              
139             return 1 if caller; # For testing
140              
141             GetOptions( \%opt, qw( missing provided ) ) or pod2usage(1);
142              
143             $activity = Term::Activity->new('Finding provided') if $SHOW_ACTIVITY;
144             my $root = $ARGV[0] // '.';
145             find( \&provided, $root );
146             if ( $opt{'provided'} ) { undef $activity; say foreach sort keys %provided; exit }
147              
148             $activity->relabel('Finding required') if $SHOW_ACTIVITY;
149             find( \&required, $root );
150             $activity->relabel('Done.') if $SHOW_ACTIVITY;
151             undef $activity;
152             my @packages = sort grep { !exists $provided{$_} } keys %required;
153             if ( !$opt{'missing'} ) { say foreach @packages; exit }
154              
155             say foreach grep { !eval "require $_" } @packages; ## no critic (ProhibitStringyEval)
156              
157             exit;