File Coverage

blib/lib/Mojolicious/Command/Author/generate/cpanfile.pm
Criterion Covered Total %
statement 89 89 100.0
branch 37 40 92.5
condition 12 12 100.0
subroutine 16 16 100.0
pod 1 1 100.0
total 155 158 98.1


line stmt bran cond sub pod time code
1             package Mojolicious::Command::Author::generate::cpanfile;
2              
3             our $VERSION = '0.20';
4              
5 1     1   295608 use 5.018;
  1         5  
6              
7 1     1   7 use List::Util 'reduce';
  1         16  
  1         76  
8 1     1   7 use Mojo::Base 'Mojolicious::Command';
  1         2  
  1         7  
9 1     1   280 use Mojo::Collection 'c';
  1         3  
  1         74  
10 1     1   7 use Mojo::File 'path';
  1         2  
  1         52  
11 1     1   7 use Mojo::Util 'getopt';
  1         3  
  1         43  
12 1     1   584 use Perl::Tokenizer;
  1         6992  
  1         81  
13 1     1   546 use version 0.77;
  1         1992  
  1         8  
14              
15             has description => 'Generate "cpanfile"';
16              
17             has usage => sub { shift->extract_usage };
18              
19             sub run {
20 3     3 1 37058 my ($self, @args) = @_;
21 3         13 my $path = path;
22 3         72 my $lib = c;
23 3         44 my $requires = {Mojolicious => 1};
24 3         11 my $t = c;
25 3         24 my $test_requires = {};
26 3         9 my $packages = {};
27 3         8 my $versions = {};
28              
29             getopt(
30             \@args,
31 1     1   95 'l|lib=s' => sub { push @$lib, $path->child($_[1]) },
32 2     2   659 'r|requires=s' => sub { ++$requires->{$_[1]} },
33 1     1   116 't=s' => sub { push @$t, $path->child($_[1]) },
34             )
35 3 100       37 or return;
36              
37 2 100       421 push @$lib, $path->child('lib') unless $lib->size;
38 2 100       85 push @$t, $path->child('t') unless $t->size;
39              
40 2         36 $self->_find_dependencies($lib, $requires, $packages, $versions);
41 2         10 $self->_find_dependencies($t, $test_requires, $packages, $versions, 1);
42              
43 2         9 delete @$test_requires{keys %$requires};
44              
45             # add "perl" to requirements if (use|require) $version exists in sources
46 2 100       18 $requires->{perl} = 1 if $versions->{perl};
47              
48 2         10 $self->_set_versions($requires, $versions);
49 2         7 $self->_set_versions($test_requires, $versions);
50              
51             $self->render_to_rel_file(
52             'cpanfile',
53             'cpanfile',
54             {
55 2         26 perl => delete($requires->{perl}),
56             requires => $requires,
57             test_requires => $test_requires,
58             });
59             }
60              
61             sub _find_dependencies {
62 4     4   13 my ($self, $paths, $requires, $packages, $module_versions, $test) = @_;
63 4 100       22 my $match = $test ? qr/\.(pm|t)$/ : qr/\.pm$/;
64              
65             $paths->uniq->each(sub {
66             shift->list_tree->grep($match)->each(sub {
67 5         779 my $file = shift;
68 5         35 my $code = $file->slurp;
69 5         94 my ($keyword, $module);
70              
71             perl_tokens {
72 328         15373 my $token = $_[0];
73              
74 328 100 100     1134 return if $token eq 'horizontal_space' or $token eq 'vertical_space';
75              
76 202         842 my $value = substr($code, $_[1], $_[2] - $_[1]);
77              
78 202 100       567 if ($token eq 'keyword') {
    100          
79 32 100 100     127 if ($value eq 'package' or $value eq 'use' or $value eq 'require') {
      100        
80 18         35 $keyword = $value;
81 18         41 undef $module;
82             }
83             else {
84 14         32 undef $keyword;
85             }
86             }
87             elsif ($keyword) {
88 33 100 100     98 if ($token eq 'bare_word') {
    100          
89 18 100       96 if ($keyword eq 'package') {
    100          
    50          
90 1         3 ++$packages->{$value};
91 1         5 undef $keyword;
92             }
93             elsif ($keyword eq 'use') {
94             # use if followed by module name and potentially a version
95 15 100       36 unless ($module) {
96 13         20 $module = $value;
97 13         51 ++$requires->{$module};
98             }
99             }
100             elsif ($keyword eq 'require') {
101             # require if followed by module name but no additional version number
102 2         9 ++$requires->{$value};
103 2         6 undef $keyword;
104             }
105             }
106             elsif ($token eq 'number' or $token eq 'v_string') {
107 5 100       15 if ($keyword eq 'use') {
    50          
108 4 100       10 if ($module) { # use Module::Name 0.12
109 3         6 push @{$module_versions->{$module}}, $value;
  3         10  
110 3         9 undef $module;
111             }
112             else { # use 5.24.3
113 1         2 push @{$module_versions->{perl}}, $value;
  1         6  
114             }
115             }
116             elsif ($keyword eq 'require') {
117 1         2 push @{$module_versions->{perl}}, $value;
  1         4  
118             }
119              
120 5         13 undef $keyword;
121             }
122             else {
123 10         19 undef $keyword;
124 10         29 undef $module;
125             }
126             }
127 5         33 } $code;
128 4     4   154 });
129 4         23 });
130              
131 4         347 delete @$requires{keys %$packages}; # remove own modules
132              
133 4         14 return $self;
134             }
135              
136             sub _set_versions {
137 4     4   10 my ($self, $r, $v) = @_;
138              
139 4         13 foreach my $module_name (keys %$r) {
140 17 100       41 if (my $module_versions = $v->{$module_name}) {
141 4 50   1   48 $r->{$module_name} = reduce { version->parse($a) > version->parse($b) ? $a : $b } @$module_versions;
  1         31  
142             }
143             else {
144 13         28 $r->{$module_name} = undef;
145             }
146             }
147             }
148              
149             1;
150              
151             =encoding utf8
152              
153             =head1 NAME
154              
155             Mojolicious::Command::Author::generate::cpanfile - cpanfile generator command
156              
157             =head1 SYNOPSIS
158              
159             Usage: APPLICATION generate cpanfile [OPTIONS]
160              
161             mojo generate cpanfile
162             mojo generate cpanfile -r Mojolicious::Plugin::OpenAPI
163             mojo generate cpanfile -l lib -l src -t t -t xt
164              
165             Options:
166             -h, --help Show this summary of available options
167             -l, --lib Overwrite module directories in which to look for
168             dependencies. Can be used multiple times.
169             Defaults to 'lib' if no -l option is used.
170             -r, --requires Add module to dependencies that can't be found by
171             scanner. Can be used multiple times.
172             -t Overwrite test directories in which to look for
173             test dependencies. Can be used multiple times.
174             Defaults to 't' if no -t option is used.
175              
176             =head1 DESCRIPTION
177              
178             L generates a C file
179             by analyzing the application source code. It scans the C<*.pm> files in the
180             directories under F<./lib> (or whatever is given by the C<-l> option) for
181             regular module dependencies and C<*.t> files in F<./t> (or whatever is given by
182             the C<-t> option) for test dependencies.
183              
184             =head1 ATTRIBUTES
185              
186             L inherits all attributes from
187             L and implements the following new ones.
188              
189             =head2 description
190              
191             my $description = $cpanfile->description;
192             $cpanfile = $cpanfile->description('Foo');
193              
194             Short description of this command, used for the command list.
195              
196             =head2 usage
197              
198             my $usage = $cpanfile->usage;
199             $cpanfile = $cpanfile->usage('Foo');
200              
201             Usage information for this command, used for the help screen.
202              
203             =head1 METHODS
204              
205             L inherits all methods from
206             L and implements the following new ones.
207              
208             =head2 run
209              
210             $cpanfile->run(@ARGV);
211              
212             Run this command.
213              
214             =head1 LICENSE
215              
216             Copyright (C) Bernhard Graf.
217              
218             This library is free software; you can redistribute it and/or modify
219             it under the same terms as Perl itself.
220              
221             =head1 AUTHOR
222              
223             Bernhard Graf Eaugensalat@gmail.comE
224              
225             =cut
226              
227             =head1 SEE ALSO
228              
229             L, L, L.
230              
231             =cut
232              
233             __DATA__