File Coverage

blib/lib/P5U/Command/Deps.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package P5U::Command::Deps;
2              
3 1     1   26650 use 5.010;
  1         4  
  1         46  
4 1     1   6 use strict;
  1         2  
  1         35  
5 1     1   1156 use utf8;
  1         15  
  1         6  
6 1     1   540 use P5U-command;
  0            
  0            
7              
8             BEGIN {
9             $P5U::Command::Deps::AUTHORITY = 'cpan:TOBYINK';
10             $P5U::Command::Deps::VERSION = '0.005';
11             }
12              
13             use constant abstract => q <scan Perl source code for dependencies>;
14             use constant command_names => qw <deps dependencies scandeps>;
15             use constant usage_desc => q <%c deps %o FILE(s)?>;
16              
17             sub opt_spec
18             {
19             return (
20             [ 'format|f=s' => 'output format (text, mi, pretdsl)' ],
21             [ 'skip-core|skipcore|c' => 'skip core modules' ],
22             [ 'keep-provided|k' => "don't skip provided modules" ],
23             );
24             }
25              
26             sub execute
27             {
28             my ($self, $opt, $args) = @_;
29            
30             my @files = map {
31             -d $_
32             ? $self->_mk_rule->all($_)
33             : "Path::Tiny"->new($_)
34             } @$args;
35             @files = $self->_mk_rule->all unless @$args;
36            
37             my $deps = $self->_get_deps(@files);
38             $self->_whittle_provides($deps, \@files) unless $opt->keep_provided;
39             $self->_whittle_corelist($deps) if $opt->skip_core;
40             print $self->_output($deps, $opt->{format});
41             }
42              
43             sub _mk_rule
44             {
45             require Path::Iterator::Rule;
46             "Path::Iterator::Rule"->new->skip_vcs->nonempty->perl_file;
47             }
48              
49             sub _get_deps
50             {
51             my ($self, @files) = @_;
52            
53             require CPAN::Meta::Requirements;
54            
55             my ($BUILD, $TEST, $XTEST, $RUNTIME) = map {
56             "CPAN::Meta::Requirements"->new;
57             } 1..4;
58            
59             require Perl::PrereqScanner;
60             my $scan = "Perl::PrereqScanner"->new;
61            
62             for (@files)
63             {
64             my $R
65             = m{\.PL$} ? $BUILD
66             : m{(^|[/\\])xt[/\\]} ? $XTEST
67             : m{(^|[/\\])t[/\\]} ? $TEST
68             : m{\.t$} ? $TEST
69             : $RUNTIME;
70             $R->add_requirements( $scan->scan_file("$_") );
71             }
72            
73             +{
74             BUILD => $BUILD,
75             TEST => $TEST,
76             XTEST => $XTEST,
77             RUNTIME => $RUNTIME,
78             };
79             }
80              
81             sub _whittle_corelist
82             {
83             my ($self, $deps, $perlver) = @_;
84            
85             require Module::CoreList;
86            
87             $perlver //= 0 + $deps->{RUNTIME}->as_string_hash->{perl};
88             $self->usage_error("no Perl version listed, so cannot --skip-core")
89             unless $Module::CoreList::version{$perlver};
90             my $core = bless $Module::CoreList::version{$perlver}, 'Module::CoreList';
91            
92             for my $d (values %$deps)
93             {
94             for my $module ($d->required_modules)
95             {
96             # whittle magic Module::Install stuff
97             if ($module =~ /^inc::Module::/)
98             {
99             $d->clear_requirement($module);
100             }
101            
102             # skip modules never in core, or ever removed from core
103             next unless $core->first_release($module);
104             next if $core->removed_from($module);
105            
106             # whittle modules if the core version meets requirement
107             if ($d->accepts_module($module, $core->{$module}))
108             {
109             $d->clear_requirement($module);
110             }
111             }
112             }
113             }
114              
115             sub _whittle_provides
116             {
117             my ($self, $deps, $files) = @_;
118            
119             require Class::Discover;
120             require List::MoreUtils;
121            
122             my @keywords = qw( package );
123            
124             for my $d (values %$deps)
125             {
126             my $hashref = $d->as_string_hash;
127             push @keywords => qw( class role )
128             if exists $hashref->{"MooseX::Declare"};
129             push @keywords => qw( class role exception )
130             if exists $hashref->{"MooseX::DeclareX"};
131             push @keywords => qw( class role application controller controller_role view model )
132             if exists $hashref->{"CatalystX::Declare"};
133             }
134            
135             my $packages = "Class::Discover"->discover_classes({
136             files => $files,
137             keywords => [ List::MoreUtils::uniq(@keywords) ],
138             });
139            
140             my $modules = [
141             map { s{/}{::}g; $_ }
142             map { m{^lib/(.+)\.pm$} ? $1 : () }
143             @$files
144             ];
145            
146             for my $d (values %$deps)
147             {
148             $d->clear_requirement($_) for map { keys %$_ } @$packages;
149             $d->clear_requirement($_) for @$modules;
150             }
151             }
152              
153             sub _output
154             {
155             my ($self, $deps, $format) = @_;
156             $format =~ /pret/i and return $self->_output_pretdsl($deps);
157             $format =~ /mi/i and return $self->_output_mi($deps);
158            
159             for my $key (sort keys %$deps)
160             {
161             next unless $deps->{$key}->required_modules;
162             print "# $key\n";
163             for my $mod (sort $deps->{$key}->required_modules)
164             {
165             printf "%s %s\n", $mod, $deps->{$key}->requirements_for_module($mod);
166             }
167             }
168             }
169              
170             my %term = (
171             TEST => 'test_requires',
172             BUILD => 'configure_requires',
173             RUNTIME => 'requires',
174             );
175              
176             sub _output_pretdsl
177             {
178             my ($self, $deps) = @_;
179            
180             print "[\n";
181             for my $key (sort keys %$deps)
182             {
183             next unless exists $term{$key};
184             for my $mod (sort $deps->{$key}->required_modules)
185             {
186             next if $mod eq 'perl';
187             printf
188             "\t%s p`%s %s`;\n",
189             $term{$key},
190             $mod,
191             $deps->{$key}->requirements_for_module($mod),
192             ;
193             }
194             }
195             print "].\n";
196             }
197              
198             sub _output_mi
199             {
200             my ($self, $deps) = @_;
201            
202             for my $key (sort keys %$deps)
203             {
204             next unless exists $term{$key};
205             for my $mod (sort $deps->{$key}->required_modules)
206             {
207             next if $mod eq 'perl';
208             printf
209             "%s \"%s\" => %s;\n",
210             $term{$key},
211             $mod,
212             $deps->{$key}->requirements_for_module($mod),
213             ;
214             }
215             }
216             }
217              
218             1;
219              
220             __END__
221              
222             =head1 NAME
223              
224             P5U::Command::Deps - p5u plugin to scan a file or directory for Perl dependencies
225              
226             =head1 SYNOPSIS
227              
228             $ p5u deps lib/Foo/Bar.pm
229             # RUNTIME
230             Foo 1.000
231             constant 0
232             perl 5.010
233             strict 0
234             utf8 0
235              
236             $ p5u deps --skipcore lib/Foo/Bar.pm
237             # RUNTIME
238             Foo 1.000
239              
240             =head1 DESCRIPTION
241              
242             Given a list of filenames and/or directories, uses L<Perl::PrereqScanner>
243             to calculate a combined list of dependencies. If no filenames are given,
244             then the current directory is assumed.
245              
246             It uses file naming conventions to attempt to classify dependencies as
247             "runtime", "test" and "build".
248              
249             Unless using the C<< --keep-provided >> option, will use Class::Discover to
250             discover what packages are provided by the codebase, and skip those. For
251             example, if there's a lib/Foo/Bar.pm providing Foo::Bar, and the test suite
252             loads that module, it won't be included as a test_requires dependency.
253              
254             With the C<< --skip-core >> option, will also skip dependencies that are
255             satisfied by Perl core. This requires at least one C<< use VERSION >> line
256             in the files being scanned.
257              
258             Output is in the text format shown above, but with C<< --format=mi >>
259             will attempt to output L<Module::Install>-style requirements. With
260             C<< --format=pretdsl >> will output data in a format suitable for
261             L<RDF::TrineX::Parser::Pretdsl>.
262              
263             =head1 BUGS
264              
265             Please report any bugs to
266             L<http://rt.cpan.org/Dist/Display.html?Queue=P5U-Command-Deps>.
267              
268             =head1 SEE ALSO
269              
270             L<P5U>.
271              
272             L<Class::Discover>,
273             L<Perl::PrereqScanner>,
274             L<App::PrereqGrapher>.
275              
276             =head1 AUTHOR
277              
278             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
279              
280             =head1 COPYRIGHT AND LICENCE
281              
282             This software is copyright (c) 2012-2013 by Toby Inkster.
283              
284             This is free software; you can redistribute it and/or modify it under
285             the same terms as the Perl 5 programming language system itself.
286              
287             =head1 DISCLAIMER OF WARRANTIES
288              
289             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
290             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
291             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
292