File Coverage

blib/lib/App/findeps.pm
Criterion Covered Total %
statement 100 139 71.9
branch 42 96 43.7
condition 14 47 29.7
subroutine 16 18 88.8
pod 0 5 0.0
total 172 305 56.3


line stmt bran cond sub pod time code
1             package App::findeps;
2              
3 1     1   779 use strict;
  1         2  
  1         30  
4 1     1   5 use warnings;
  1         2  
  1         29  
5 1     1   5 use feature qw(state);
  1         1  
  1         123  
6              
7             our $VERSION = "0.13";
8              
9 1     1   8 use Carp qw(carp croak);
  1         1  
  1         55  
10 1     1   1455 use ExtUtils::Installed;
  1         118349  
  1         43  
11 1     1   7 use List::Util qw(first);
  1         2  
  1         62  
12              
13 1     1   3823 use Module::CoreList;
  1         147218  
  1         15  
14 1     1   1646 use Directory::Iterator;
  1         7503  
  1         2075  
15              
16             our $Upgrade = 0;
17             our $myLib = 'lib';
18             our $toCpanfile = 0;
19             my $qr4ext = qr/\w+\.(p[ml]|t|cgi|psgi)$/;
20             my $qr4name = qr/[a-zA-Z][a-zA-Z\d]+(?:::[a-zA-Z\d]+){0,}/;
21              
22             sub scanDir { # To Do
23 0   0 0 0 0 my $dir = shift || $myLib || 'lib';
24 0 0       0 die "$dir is not a directory" unless -d $dir;
25              
26 0         0 my @list = ();
27 0         0 my $list = Directory::Iterator->new($dir);
28 0         0 while ( $list->next ) {
29 0         0 my $file = $list->get;
30 0 0       0 next unless $file =~ /\.p[lm]$/;
31 0         0 scan( file => $file, list => \@list );
32             }
33 0         0 return @list;
34             }
35              
36             sub scan {
37 1     1 0 625 my %args = @_;
38 1         3 my %pairs;
39 1         2 while ( my $file = shift @{ $args{files} } ) {
  2         18  
40 1         15 ( my $ext = $file ) =~ $qr4ext;
41 1 50       10 warn "Invalid extension was set: $ext" unless $1;
42 1 50       66 open my $fh, '<', $file or die "Can't open < $file: $!";
43 1         43 while (<$fh>) {
44 7         15 chomp;
45 7 50       17 next unless defined $_;
46 7 50       20 last if /^__(?:END|DATA)__$/;
47 7         10 state( $pod, $here, $eval );
48 7 50 33     20 next if $pod and $_ ne '=cut';
49 7 50 33     18 next if $here and $_ ne $here;
50 7 50 33     67 if ( !$pod and /^=(\w+)/ ) {
    50 33        
51 0         0 $pod = $1;
52 0         0 next;
53             } elsif ( $pod and $_ eq '=cut' ) {
54 0         0 undef $pod;
55 0         0 next;
56             }
57 7 50 33     42 if ( !$here and my @catch = /(?:<<(['"])?(\w+)\1?){1,}/g ) {
    50 33        
58 0         0 $here = $catch[-1];
59 0         0 next;
60             } elsif ( $here and $_ eq $here ) {
61 0         0 undef $here;
62 0         0 next;
63             }
64 7 50       17 next if /^\s*#.*/;
65 7         19 s/\s+#.*$//;
66 7 50 33     44 if ( !$eval and /eval\s*(['"{])$/ ) {
    50 33        
    50 33        
67 0 0       0 $eval = $1 eq '{' ? '}' : $1;
68             } elsif ( $eval and /$eval(?:.*)?;$/ ) {
69 0         0 undef $eval;
70 0         0 next;
71             } elsif ( $eval and /\b(require|use)\s+($qr4name)/ ) {
72 0         0 warnIgnored( $2, $1, 'eval' );
73             }
74 7 50       17 next if $eval;
75              
76 7 100       25 if (/\buse\b/) {
77 3         9 scan_line( \%pairs, $_ );
78 3         12 next;
79             }
80              
81 4         8 state $if = 0;
82 4 50 33     27 if (/^\b(?:if|unless)\s*\(.*\)\s*{$/) {
    50 33        
    50          
83 0         0 $if++;
84             } elsif ( $if > 0 and /^\s*}$/ ) {
85 0         0 $if--;
86 0 0       0 warn "something wrong to parse: $file" if $if < 0;
87 0         0 next;
88             } elsif ( $if > 0 and /^\brequire\s+($qr4name)/ ) {
89 0         0 warnIgnored( 'require', $1, 'if' );
90             }
91 4 100       39 next unless /\b(require|use)\s+/;
92 1         6 scan_line( \%pairs, $_ );
93             }
94 1         29 close $fh;
95             }
96 1         6 my $deps = {};
97 1         4 my @local = ();
98 1         14 my $list = Directory::Iterator->new($myLib)
99             ; # To Do: $myLib must be got from local::lib within plenv/PerlBrew
100 1         1958 while ( $list->next ) {
101 1         11 my $file = $list->get;
102 1 50       11 next unless $file =~ s!\.pm$!!;
103 1         6 $file =~ s!/!::!g;
104 1         23 push @local, $file;
105             }
106              
107 1         9 while ( my ( $name, $version ) = each %pairs ) {
108 1 50       5 next if !defined $name;
109 1 50       4 next if exists $deps->{$name};
110 1 50   1   9 next if first { $_ eq $name } @local;
  1         4  
111 1 0 33     9 $deps->{$name} = $version if !defined $version or $Upgrade or $toCpanfile;
      0        
112             }
113 1         10 return $deps;
114             }
115              
116             # subroutines #----#----#----#----#----#----#----#----#----#----#----#----#
117             my @pragmas = qw(
118             attributes autodie autouse
119             base bigint bignum bigrat blib bytes
120             charnames constant diagnostics encoding
121             feature fields filetest if integer less lib locale mro
122             open ops overload overloading parent re
123             sigtrap sort strict subs
124             threads threads::shared utf8 vars vmsish
125             warnings warnings::register
126             );
127              
128             sub scan_line {
129 4     4 0 8 my $pairs = shift;
130 4         8 local $_ = shift;
131 4         6 my @names = ();
132 4 50       9 return if /^\buse\s+v5(?:\.\d{2}){1,2}\s*;/; #ignore VERSION
133 4 50       14 return if /^\buse\s+5\.\d{3}(?:_\d{3})?;/; #ignore old version
134              
135 4 50 33     310 if (/use\s+(?:base|parent)\s+qw[\("']\s*((?:$qr4name\s*){1,})[\)"']/) {
    50          
    50          
    50          
    50          
    0          
    0          
136 0         0 push @names, split /\s+/, $1;
137             } elsif (/use\s+(?:base|parent|autouse)\s+(['"])?($qr4name)\1?/) {
138 0         0 $names[0] = $2;
139             } elsif (/eval\s*(['"{])\s*(require|use)\s+($qr4name).*(?:\1|})/) {
140 0         0 warnIgnored( $3, $2, 'eval' );
141             } elsif ( /(?:if|unless)\s+\(.*\)\s*\{.*require\s+($qr4name).*\}/
142             or /require\s+($qr4name)\s+(?:if|unless)\s+\(?.*\)?/ )
143             {
144 0         0 warnIgnored( $1, 'require', 'if' );
145             } elsif (/^\s*(?:require|use)\s+($qr4name)/) {
146 4         14 $names[0] = $1;
147             } elsif (m!^\s*require\s*(["'])((?:\./)?(?:\w+/){0,}$qr4name\.pm)\1!) {
148 0         0 $names[0] = _name($2);
149             } elsif (/^\s*(require|use)\s+(['"]?)(.*)\2/) {
150 0         0 my $name = $3;
151 0 0       0 my $exists = ( -e "$myLib/$name" ) ? 'exists' : "does not exist in $myLib";
152 0         0 warn "just detected but not listed: $name($exists) $1d\n";
153             }
154 4         9 for my $name (@names) {
155 4 50       10 next unless defined $name;
156 4 50       10 next if exists $pairs->{$name};
157 4 50       8 next if $name eq 'Plack::Builder';
158 4 100   128   26 next if first { $name eq $_ } @pragmas;
  128         154  
159 1 50 33     14 next if !$Upgrade and Module::CoreList->is_core($name);
160 1         650 $pairs->{$name} = get_version($name);
161             }
162 4         29 return %$pairs;
163             }
164              
165             sub get_version {
166 1     1 0 2 my $name = shift;
167 1         10 my $installed = ExtUtils::Installed->new( skip_cwd => 1 );
168 1     60   582085 my $module = first { $_ eq $name } $installed->modules();
  60         353  
169 1         15 my $version = eval { $installed->version($module) };
  1         10  
170 1 50       424 return $version if defined $version;
171 1 50   1   959 eval "use lib '$myLib'; require $name" or return undef;
  1         1347  
  1         12  
  1         193  
172 0         0 return eval "no strict 'subs';\$${name}::VERSION";
173             }
174              
175             sub warnIgnored {
176 0     0 0 0 my $name = shift;
177 0         0 my $func = shift;
178 0         0 my $cmd = shift;
179 0 0       0 warn "$name is ${func}d inside of '$cmd'\n" unless Module::CoreList->is_core($name);
180             }
181              
182             sub _name {
183 1     1   15 my $str = shift;
184 1 50       6 $str =~ s!/!::!g if $str =~ /\.pm$/;
185 1         3 $str =~ s!^lib::!!;
186 1         3 $str =~ s!\.pm$!!;
187 1         2 $str =~ s!^auto::(.+)::.*!$1!;
188 1         3 return $str;
189             }
190              
191             1;
192              
193             __END__