File Coverage

blib/lib/App/pmdeps.pm
Criterion Covered Total %
statement 136 156 87.1
branch 30 32 93.7
condition 10 11 90.9
subroutine 23 24 95.8
pod 0 6 0.0
total 199 229 86.9


line stmt bran cond sub pod time code
1             package App::pmdeps;
2 6     6   249235 use strict;
  6         154  
  6         215  
3 6     6   37 use warnings;
  6         13  
  6         202  
4 6     6   1112 use utf8;
  6         33  
  6         42  
5 6     6   142 use Carp;
  6         10  
  6         404  
6 6     6   4295 use File::Spec::Functions qw/catfile rel2abs/;
  6         4135  
  6         441  
7 6     6   5020 use Furl;
  6         187137  
  6         230  
8 6     6   8127 use Getopt::Long qw/:config posix_default no_ignore_case bundling auto_help/;
  6         76131  
  6         49  
9 6     6   7911 use JSON;
  6         82518  
  6         50  
10 6     6   21570 use Module::CoreList;
  6         325590  
  6         96  
11 6     6   12093 use Term::ANSIColor qw/colored/;
  6         44124  
  6         3490  
12              
13             our $VERSION = "0.02";
14              
15             $ENV{ANSI_COLORS_DISABLED} = 1 if $^O eq 'MSWin32';
16              
17 6     6   71 use constant METACPAN_API_URL => 'http://api.metacpan.org/v0/release/_search';
  6         12  
  6         14633  
18              
19             sub new {
20 18     18 0 280403 my ($class) = @_;
21 18         224 bless { timeout => 10, }, $class;
22             }
23              
24             sub run {
25 16     16 0 7707 my ( $self, @args ) = @_;
26              
27 16         63 local @ARGV = @args;
28 16 100       295 GetOptions(
29             't|timeout=i' => \$self->{timeout},
30             'p|perl-version=f' => \$self->{perl_version},
31             'l|local=s', => \$self->{local},
32             'without-phase=s@' => \$self->{without_phase},
33             'without-type=s@' => \$self->{without_type},
34             'h|help!' => \$self->{usage},
35             'v|version!' => \$self->{version},
36             ) or $self->show_usage;
37              
38 15 100       12612 $self->show_version if $self->{version};
39 13 100       66 $self->show_usage if $self->{usage};
40              
41 11 100       42 if ($self->{without_phase}) {
42 2         22 @{$self->{without_phase}} = split( /,/, join(',', @{$self->{without_phase}}) );
  2         10  
  2         11  
43             }
44              
45 11 100       42 if ($self->{without_type}) {
46 2         4 @{$self->{without_type}} = split( /,/, join(',', @{$self->{without_type}}) );
  2         7  
  2         11  
47             }
48              
49 11 100 100     97 $self->show_short_usage unless ( @ARGV || $self->{local} );
50              
51 10   66     36 $self->{perl_version} ||= $];
52 10         50 $self->show_dependencies(@ARGV);
53             }
54              
55             sub show_dependencies {
56 10     10 0 25 my ( $self, @args ) = @_;
57              
58 10         17 my $deps;
59 10 100       37 if ( $self->{local} ) {
60 8         46 $deps = $self->_fetch_deps_from_metadata( $self->{local} );
61             }
62             else {
63 2         15 $deps = $self->_fetch_deps_from_metacpan( { name => $args[0], version => $args[1] } );
64             }
65 9         255 my ( $cores, $non_cores ) = $self->_divide_core_or_not($deps);
66 9         62 $self->_spew( $cores, $non_cores );
67             }
68              
69             sub _spew {
70 9     9   23 my ( $self, $cores, $non_cores ) = @_;
71              
72 9         49 my $core_index = $self->_make_index( scalar(@$cores) );
73 9         30 my $non_core_index = $self->_make_index( scalar(@$non_cores), 'non-' );
74              
75 9         996 print "Target: perl-$self->{perl_version}\n";
76 9         119 print colored['green'], "$core_index";
77 9         374 print "\n";
78 9         186 print "\t$_\n" for (@$cores);
79 9         53 print colored['yellow'], "$non_core_index";
80 9         274 print "\n";
81 9         512 print "\t$_\n" for (@$non_cores);
82             }
83              
84             sub _make_index {
85 18     18   37 my ( $self, $num, $optional ) = @_;
86              
87 18   100     69 $optional ||= '';
88 18         61 my $index = "Depends on $num " . $optional . "core modules:";
89 18 100       53 if ( $num == 1 ) {
90 5         39 $index =~ s/modules/module/;
91             }
92 18 100       51 unless ($num) {
93 1         3 $index = "Depends on no " . $optional . "core module.";
94             }
95              
96 18         43 return $index;
97             }
98              
99             sub _fetch_deps_from_metacpan {
100 0     0   0 my ( $self, $module ) = @_;
101              
102 0         0 ( my $module_name = $module->{name} ) =~ s/::/-/g;
103 0         0 my $module_version = $module->{version};
104              
105 0         0 my $version_dscr = '"term": { "release.status": "latest" }';
106 0 0       0 if ($module_version) {
107 0         0 $version_dscr = qq/"term": { "release.version": "$module_version" }/;
108             }
109              
110 0         0 my $furl = Furl->new(
111             agent => 'App-pmdeps',
112             timeout => $self->{timeout},
113             );
114              
115 0         0 my $res = $furl->post(
116             METACPAN_API_URL,
117             [ 'Content-Type' => 'application/json' ],
118             sprintf( <<'EOQ', $module_name, $version_dscr ) );
119             {
120             "query": {
121             "match_all": {}
122             },
123             "fields": [ "dependency" ],
124             "filter": {
125             "and": [
126             { "term": { "release.distribution": "%s" } },
127             { "term": { "release.maturity": "released" } },
128             { %s }
129             ]
130             }
131             }
132             EOQ
133              
134 0         0 my $content = decode_json( $res->{content} );
135 0         0 my @deps = @{$content->{hits}->{hits}[0]->{fields}->{dependency}};
  0         0  
136 0         0 for my $phase (@{$self->{without_phase}}) {
  0         0  
137 0         0 @deps = grep { $_->{phase} ne $phase } @deps;
  0         0  
138             }
139 0         0 for my $type (@{$self->{without_type}}) {
  0         0  
140 0         0 @deps = grep { $_->{relationship} ne $type } @deps;
  0         0  
141             }
142              
143 0         0 return \@deps;
144             }
145              
146             sub _fetch_deps_from_metadata {
147 8     8   16 my ( $self, $path ) = @_;
148              
149 8         54 $path = rel2abs($path);
150              
151 8         194 my $meta_json_file = catfile( $path, 'META.json' );
152 8         35 my $mymeta_json_file = catfile( $path, 'MYMETA.json' );
153              
154 8         16 my $using_json_file;
155 8 100       300 $using_json_file = $mymeta_json_file if -e $mymeta_json_file;
156 8 100       154 $using_json_file = $meta_json_file if -e $meta_json_file; # <= High priority
157              
158 8 100       41 unless ($using_json_file) {
159 1         195 croak '[ERROR] META.json or MYMETA.json is not found.';
160             }
161              
162 7         31 local $/;
163 7         307 open my $fh, '<', $using_json_file;
164 7         595 my $json = decode_json(<$fh>);
165 7         90 close $fh;
166              
167 7         13 my @prereqs;
168 7         11 for my $phase ( keys %{ $json->{prereqs} } ) {
  7         37  
169 21 100       29 unless ( grep { $phase eq $_ } @{ $self->{without_phase} } ) {
  12         36  
  21         61  
170 17         46 push @prereqs, $json->{prereqs}->{$phase};
171             }
172             }
173              
174 7         18 for my $prereq (@prereqs) {
175 17         24 for my $type ( @{ $self->{without_type} } ) {
  17         39  
176 12         31 delete $prereq->{$type};
177             }
178             }
179              
180 7         13 my @requires;
181 7         16 my @modules = map { keys %$_ } map { values %$_ } @prereqs;
  30         79  
  17         50  
182 7         18 for my $module ( @modules ) {
183 59         127 push @requires, { module => $module };
184             }
185 7         135 return \@requires;
186             }
187              
188             sub _divide_core_or_not {
189 9     9   21 my ( $self, $deps ) = @_;
190              
191 9         14 my ( @cores, @non_cores );
192              
193 9         20 for my $dep (@$deps) {
194 63         197 my $module = $dep->{module};
195              
196 63 100       202 next if $module eq 'perl';
197              
198 56         335 my $core_version = Module::CoreList->first_release($module);
199 56 100 100     971694 if ( $core_version && $self->{perl_version} - $core_version > 0 ) {
200 14         46 push @cores, $module;
201 14         45 next;
202             }
203 42         203 push @non_cores, $module;
204             }
205              
206 9         120 @cores = sort { $a cmp $b } $self->_unique(@cores);
  5         26  
207 9         38 @non_cores = sort { $a cmp $b } $self->_unique(@non_cores);
  68         103  
208              
209 9         58 return ( \@cores, \@non_cores );
210             }
211              
212             sub show_version {
213 3     3 0 18 _print_immediately("pm-deps (App::pmdeps): v$VERSION");
214 3         38 die "\n";
215             }
216              
217             sub show_short_usage {
218 2     2 0 13 _print_immediately(<
219             Usage: pm-deps [options] Module [module_version]
220              
221             Try `pm-deps --help` to get more information.
222             EOU
223 2         34 die "\n";
224             }
225              
226             sub show_usage {
227 4     4 0 880 _print_immediately(<
228             Usage:
229             pm-deps [options] Module [module_version]
230              
231             options:
232             -l,--local Fetch dependencies from the local module
233             -p,--perl-version Set target perl version (default: perl version which you are using)
234             -t,--timeout Set seconds of the threshold for timeout (This application attempts to connect to metacpan)
235             -h,--help Show help messages. It's me!
236             -v,--version Show version of this application
237             EOU
238 4         35 die "\n";
239             }
240              
241             sub _print_immediately {
242 9     9   23 my $msg = shift;
243 9         23 $| = 1; # flush
244 9         563 print $msg;
245 9         29 $| = 0; # no flush
246             }
247              
248             sub _unique {
249 18     18   58 my ( $self, @array ) = @_;
250 18         38 my %hash = map { $_, 1 } @array;
  56         175  
251 18         117 return keys %hash;
252             }
253             1;
254             __END__