File Coverage

blib/lib/Dist/Zilla/Plugin/Author/KENTNL/RecommendFixes.pm
Criterion Covered Total %
statement 190 208 91.3
branch 41 66 62.1
condition n/a
subroutine 63 82 76.8
pod 0 4 0.0
total 294 360 81.6


line stmt bran cond sub pod time code
1 6     6   10211139 use 5.006;
  6         16  
2 6     6   21 use strict;
  6         11  
  6         118  
3 6     6   27 use warnings;
  6         7  
  6         436  
4              
5             package Dist::Zilla::Plugin::Author::KENTNL::RecommendFixes;
6              
7             our $VERSION = '0.005003';
8              
9             # ABSTRACT: Recommend generic changes to the dist.
10              
11             our $AUTHORITY = 'cpan:KENTNL'; # AUTHORITY
12              
13 6     6   520 use Moose qw( with has around );
  6         317503  
  6         43  
14 6     6   25865 use MooX::Lsub qw( lsub );
  6         5624  
  6         23  
15 6     6   2856 use Path::Tiny qw( path );
  6         7763  
  6         268  
16 6     6   702 use YAML::Tiny;
  6         4561  
  6         295  
17 6     6   2686 use Data::DPath qw( dpath );
  6         329762  
  6         36  
18 6     6   4703 use Generic::Assertions;
  6         6397  
  6         188  
19              
20             with 'Dist::Zilla::Role::InstallTool';
21              
22 6     6   3245 use Term::ANSIColor qw( colored ); () = eval { require Win32::Console::ANSI } if 'MSWin32' eq $^O;
  6         29199  
  6         18720  
23              
24             our $LOG_COLOR = 'yellow';
25              
26             around 'log' => sub {
27             my ( $orig, $self, @args ) = @_;
28             return $self->$orig( map { ref $_ ? $_ : colored( [$LOG_COLOR], $_ ) } @args );
29             };
30              
31             ## no critic (Subroutines::ProhibitSubroutinePrototypes,Subroutines::RequireArgUnpacking,Variables::ProhibitLocalVars)
32 9     9   15 sub _is_bad(&) { local $LOG_COLOR = 'red'; return $_[0]->() }
  9         18  
33              
34             sub _badly(&) {
35 18     18   19 my $code = shift;
36 18     15   319 return sub { local $LOG_COLOR = 'red'; return $code->(@_); };
  15     15   203  
  15     15   49  
        15      
37             }
38             ## use critic
39              
40             sub _after_true {
41 72     72   110 my ( $subname, $code ) = @_;
42             return around $subname, sub {
43 55     55   644 my ( $orig, $self, @args ) = @_;
44 55 100       1772 return unless my $rval = $self->$orig(@args);
45 11         111 return $code->( $rval, $self, @args );
46 72         400 };
47              
48             }
49              
50             sub _rel {
51 166     166   161 my ( $self, @args ) = @_;
52 166         4488 return $self->root->child(@args)->relative( $self->root );
53             }
54              
55             sub _mk_assertions {
56 10     10   28 my ( $self, @args ) = @_;
57             return Generic::Assertions->new(
58             @args,
59             '-handlers' => {
60             should => sub {
61 142     142   1302 my ( $status, $message, $name, @slurpy ) = @_;
62 142 100       254 if ( not $status ) {
63 127         424 $self->log("should $name: $message");
64 127         32990 return;
65             }
66 15         72 $self->log_debug("ok:should $name: $message");
67 15         3818 return $slurpy[0];
68             },
69             should_not => sub {
70 115     115   904 my ( $status, $message, $name, @slurpy ) = @_;
71 115 50       189 if ($status) {
72 0         0 $self->log("should_not $name: $message");
73 0         0 return;
74             }
75 115         422 $self->log_debug("ok:should not $name: $message");
76 115         26110 return $slurpy[0];
77             },
78             must => sub {
79 0     0   0 my ( $status, $message, $name, @slurpy ) = @_;
80 0 0       0 $self->log_fatal("must $name: $message") unless $status;
81 0         0 return $slurpy[0];
82             },
83             must_not => sub {
84 0     0   0 my ( $status, $message, $name, @slurpy ) = @_;
85 0 0       0 $self->log_fatal("must_not $name: $message") if $status;
86 0         0 return $slurpy[0];
87             },
88             },
89 10         253 );
90             }
91              
92             has _pc => ( is => ro =>, lazy => 1, builder => '_build__pc' );
93              
94             sub _build__pc {
95 5     5   9 my ($self) = @_;
96              
97 5         8 my %cache;
98              
99             my $get_lines = sub {
100 45 100   45   73 exists $cache{ $_[0] } or ( $cache{ $_[0] } = [ $_[0]->lines_raw( { chomp => 1 } ) ] );
101 45         1911 return $cache{ $_[0] };
102 5         22 };
103              
104             return $self->_mk_assertions(
105             '-input_transformer' => sub {
106 166     166   1478 my ( undef, @bits ) = @_;
107 166         186 my $path = shift @bits;
108 166         285 return ( $self->_rel($path), @bits );
109             },
110             exist => sub {
111 121 100   121   20119 if ( $_[0]->exists ) {
112 15         298 return ( 1, "$_[0] exists" );
113             }
114 106         1871 return ( 0, "$_[0] does not exist" );
115             },
116             have_line => sub {
117 39     39   5925 my ( $path, $regex ) = @_;
118 39         34 my (@lines) = @{ $get_lines->($path) };
  39         60  
119 39 100       187 return ( 0, "$path has no lines ( none to match $regex )" ) unless @lines;
120 20         52 for my $line (@lines) {
121 160 50       324 return ( 1, "$path Has line matching $regex" ) if $line =~ $regex;
122             }
123 20         43 return ( 0, "$path Does not have line matching $regex" );
124             },
125             have_one_of_line => sub {
126 6     6   874 my ( $path, @regexs ) = @_;
127 6         6 my (@rematches);
128 6         11 for my $line ( @{ $get_lines->($path) } ) {
  6         11  
129 40         55 for my $re (@regexs) {
130 80 50       151 if ( $line =~ $re ) {
131 0         0 push @rematches, "Has line matching $re";
132             }
133             }
134             }
135 6 50       22 if ( not @rematches ) {
136 6         31 return ( 0, "Does not match at least one of ( @regexs )" );
137             }
138 0 0       0 if ( @rematches > 1 ) {
139 0         0 return ( 0, "Matches more than one of ( @rematches )" );
140             }
141 0         0 return ( 1, "Matches only @rematches" );
142             },
143 5         78 );
144             }
145              
146             has _dc => ( is => ro =>, lazy => 1, builder => '_build__dc' );
147              
148             sub _build__dc {
149 5     5   8 my ($self) = @_;
150              
151 5         7 my %yaml_cache;
152              
153             my $get_yaml = sub {
154             exists $yaml_cache{ $_[0] } or (
155 12 100   12   3455 $yaml_cache{ $_[0] } = do {
156 1         6 my ( $r, $ok );
157             ## no critic (ErrorHandling::RequireCheckingReturnValueOfEval)
158 1         2 eval {
159 1         4 $r = YAML::Tiny->read( path( $_[0] )->stringify )->[0];
160 1         1764 $ok = 1;
161             };
162 1         4 $r;
163             }
164             );
165 12         53 return $yaml_cache{ $_[0] };
166 5         24 };
167              
168             return $self->_mk_assertions(
169             have_dpath => sub {
170 85     85   1261 my ( $label, $data, $expression ) = @_;
171 85 50       165 if ( dpath($expression)->match($data) ) {
172 0         0 return ( 1, "$label matches $expression" );
173             }
174 85         25004 return ( 0, "$label does not match $expression" );
175              
176             },
177             yaml_have_dpath => sub {
178 12     12   176 my ( $yaml_path, $expression ) = @_;
179 12 50       23 if ( dpath($expression)->match( $get_yaml->($yaml_path) ) ) {
180 0         0 return ( 1, "$yaml_path matches $expression" );
181             }
182 12         829 return ( 0, "$yaml_path does not match $expression" );
183              
184             },
185 5         36 );
186              
187             }
188              
189 5     5   62 lsub root => sub { my ($self) = @_; return path( $self->zilla->root ) };
  5         144  
190              
191             my %amap = (
192             git => '.git',
193             libdir => 'lib',
194             dist_ini => 'dist.ini',
195             git_config => '.git/config',
196             dist_ini_meta => 'dist.ini.meta',
197             weaver_ini => 'weaver.ini',
198             travis_yml => '.travis.yml',
199             perltidyrc => '.perltidyrc',
200             gitignore => '.gitignore',
201             changes => 'Changes',
202             license => 'LICENSE',
203             mailmap => '.mailmap',
204             perlcritic_gen => 'maint/perlcritic.rc.gen.pl',
205             perlcritic_deps => 'misc/perlcritic.deps',
206             contributing_pod => 'CONTRIBUTING.pod',
207             contributing_mkdn => 'CONTRIBUTING.mkdn',
208             makefile_pl => 'Makefile.PL',
209             install_skip => 'INSTALL.SKIP',
210             readme_pod => 'README.pod',
211             tdir => 't',
212             );
213              
214             for my $key (qw( git libdir dist_ini )) {
215             my $value = delete $amap{$key};
216             lsub $key => _badly { $_[0]->_pc->should( exist => $value ) };
217             }
218             for my $key ( keys %amap ) {
219             my $value = $amap{$key};
220 66     66   2994 lsub $key => sub { $_[0]->_pc->should( exist => $value ) };
        66      
        66      
        66      
        66      
        66      
        66      
        66      
        66      
        66      
        66      
        66      
        66      
        66      
        66      
        66      
        66      
221 0     0   0 lsub "_have_$key" => sub { $_[0]->_pc->test( exist => $value ) };
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
222             }
223              
224             _after_true makefile_pl => sub {
225             my ( $file, $self ) = @_;
226             undef $file if $self->install_skip;
227             return $file;
228             };
229              
230             _after_true contributing_pod => sub {
231             my ( $file, $self ) = @_;
232             undef $file if $self->_pc->should_not( exist => $amap{contributing_mkdn} );
233             return $file;
234             };
235              
236             _after_true gitignore => sub {
237             my ( $rval, $self, ) = @_;
238             my $file = $amap{'gitignore'};
239             my $assert = $self->_pc;
240             my $ok = $rval;
241             my $distname = $self->zilla->name;
242             undef $ok unless $assert->should( have_line => $file, qr/\A\/\.build\z/ );
243             undef $ok unless $assert->should( have_line => $file, qr/\A\/tmp\/\z/ );
244              
245             undef $ok unless $assert->should( have_line => $file, qr/\A\/\Q$distname\E-\*\z/ );
246             undef $ok unless $assert->should_not( have_line => $file, qr/\A\Q$distname\E-\*\z/ );
247              
248             if ( $self->_have_makefile_pl ) {
249             ## no critic ( RegularExpressions::ProhibitFixedStringMatches )
250             undef $ok unless $assert->should( have_line => $file, qr/\A\/META\.json\z/ );
251             undef $ok unless $assert->should( have_line => $file, qr/\A\/MYMETA\.json\z/ );
252             undef $ok unless $assert->should( have_line => $file, qr/\A\/META\.yml\z/ );
253             undef $ok unless $assert->should( have_line => $file, qr/\A\/MYMETA\.yml\z/ );
254             undef $ok unless $assert->should( have_line => $file, qr/\A\/Makefile\z/ );
255             undef $ok unless $assert->should( have_line => $file, qr/\A\/Makefile\.old\z/ );
256             undef $ok unless $assert->should( have_line => $file, qr/\A\/blib\/\z/ );
257             undef $ok unless $assert->should( have_line => $file, qr/\A\/pm_to_blib\z/ );
258             }
259             return $ok;
260             };
261              
262             _after_true install_skip => sub {
263             my ( $rval, $self, ) = @_;
264             my $skipfile = $amap{'install_skip'};
265             my (@entries) = qw( contributing_pod readme_pod );
266             my $assert = $self->_pc;
267             my $ok = $rval;
268             for my $entry (@entries) {
269             my $sub = $self->can("_have_${entry}");
270             next unless $self->$sub();
271             my $entry_re = quotemeta $amap{$entry};
272             undef $ok unless $assert->should( have_line => $skipfile, qr/\A\Q$entry_re\E\$\z/ );
273             }
274             return $ok;
275             };
276              
277 5     5   188 lsub changes_deps_files => sub { return [qw( Changes.deps Changes.deps.all Changes.deps.dev Changes.deps.all )] };
278              
279             lsub libfiles => sub {
280 2     2   25 my ($self) = @_;
281 2 50       52 return [] unless $self->libdir;
282 2         14 my @out;
283 2         49 my $it = $self->libdir->iterator( { recurse => 1 } );
284 2         62 while ( my $thing = $it->() ) {
285 6 100       636 next if -d $thing;
286 3 100       32 next unless $thing->basename =~ /\.pm\z/msx;
287 1         23 push @out, $thing;
288             }
289 2 100       90 if ( not @out ) {
290 1     1   6 _is_bad { $self->log( 'Should have modules in ' . $self->libdir ) };
  1         50  
291             }
292              
293 2         318 return \@out;
294             };
295             lsub tfiles => sub {
296 1     1   12 my ($self) = @_;
297 1 50       25 return [] unless $self->tdir;
298 1         6 my @out;
299 1         25 my $it = $self->tdir->iterator( { recurse => 1 } );
300 1         21 while ( my $thing = $it->() ) {
301 1 50       90 next if -d $thing;
302 1 50       10 next unless $thing->basename =~ /\.t\z/msx;
303 1         24 push @out, $thing;
304             }
305 1 50       19 if ( not @out ) {
306 0         0 $self->log( 'Should have tests in ' . $self->tdir );
307             }
308 1         35 return \@out;
309              
310             };
311              
312             sub has_new_changes_deps {
313 5     5 0 9 my ($self) = @_;
314 5         9 my $ok = 1;
315 5         169 my $assert = $self->_pc;
316 5         11 for my $file ( @{ $self->changes_deps_files } ) {
  5         143  
317 20 50       85 undef $ok unless $assert->should( exist => 'misc/' . $file );
318 20 50       57 undef $ok unless $assert->should_not( exist => $file );
319             }
320 5         16 return $ok;
321             }
322              
323             _after_true perlcritic_deps => sub {
324             my ( $file, $self ) = @_;
325             my $ok = $file;
326             my $assert = $self->_pc;
327             undef $ok unless $assert->should_not( exist => 'perlcritic.deps' );
328             return $ok;
329             };
330              
331             _after_true 'perlcritic_gen' => sub {
332             my ( $file, $self ) = @_;
333             my $assert = $self->_pc;
334             my $ok = $file;
335             undef $ok unless $assert->should( have_line => $file, qr/Path::Tiny/ );
336             undef $ok unless $assert->should( have_line => $file, qr/\.\/misc/ );
337             return $ok;
338             };
339              
340             _after_true 'git_config' => sub {
341             my ( $rval, $self ) = @_;
342             undef $rval unless $self->_pc->should_not( have_line => $rval, qr/kentfredric/ );
343             return $rval;
344             };
345              
346 6     6   7 sub _matrix_include_perl { my ($perl) = @_; return "/matrix/include/*/perl[ value eq \"$perl\"]"; }
  6         26  
347 4     4   4 sub _branch_only { my ($branch) = @_; return '/branches/only/*[ value eq "' . $branch . '"]' }
  4         15  
348              
349             _after_true 'travis_yml' => sub {
350             my ( $yaml, $self ) = @_;
351             my $assert = $self->_dc;
352             my $ok = $yaml;
353              
354             undef $ok unless $assert->should( yaml_have_dpath => $yaml, '/matrix/include/*/env[ value =~ /COVERAGE_TESTING=1/' );
355              
356             for my $perl (qw( 5.21 5.20 5.10 )) {
357             undef $ok unless $assert->should( yaml_have_dpath => $yaml, _matrix_include_perl($perl) );
358             }
359             for my $perl (qw( 5.8 )) {
360             undef $ok unless $assert->should( yaml_have_dpath => $yaml, _matrix_include_perl($perl) );
361             }
362             for my $perl (qw( 5.19 )) {
363             undef $ok unless _is_bad { $assert->should_not( yaml_have_dpath => $yaml, _matrix_include_perl($perl) ) };
364             }
365             for my $perl (qw( 5.18 )) {
366             undef $ok unless $assert->should_not( yaml_have_dpath => $yaml, _matrix_include_perl($perl) );
367             }
368             undef $ok
369             unless _is_bad { $assert->should( yaml_have_dpath => $yaml, '/before_install/*[ value =~/git clone.*maint-travis-ci/ ]' ) };
370             for my $branch (qw( master builds releases )) {
371             undef $ok unless $assert->should( yaml_have_dpath => $yaml, _branch_only($branch) );
372             }
373             for my $branch (qw( build/master )) {
374             undef $ok unless $assert->should_not( yaml_have_dpath => $yaml, _branch_only($branch) );
375             }
376              
377             return $ok;
378             };
379              
380             _after_true 'dist_ini' => sub {
381             my ( $ini, $self ) = @_;
382             my $assert = $self->_pc;
383             my $ok = $ini;
384             my (@tests) = ( qr/dzil bakeini/, qr/normal_form\s*=\s*numify/, qr/mantissa\s*=\s*6/, );
385             for my $test (@tests) {
386             undef $ok unless $assert->should( have_line => $ini, $test );
387             }
388             if ( not $assert->test( have_line => $ini, qr/dzil bakeini/ ) ) {
389             _is_bad { undef $ok unless $assert->should( have_one_of_line => $ini, qr/bumpversions\s*=\s*1/, qr/git_versions/ ) };
390             }
391             return $ok;
392             };
393              
394             _after_true 'weaver_ini' => sub {
395             my ( $weave, $self ) = @_;
396             my $assert = $self->_pc;
397             my $ok = $weave;
398             undef $ok unless $assert->should( have_line => $weave, qr/-SingleEncoding/, );
399             undef $ok unless $assert->should_not( have_line => $weave, qr/-Encoding/, );
400             return $ok;
401             };
402              
403             _after_true 'dist_ini_meta' => sub {
404             my ( $file, $self ) = @_;
405             my $assert = $self->_pc;
406             my (@wanted_regex) = (
407             qr/bumpversions\s*=\s*1/, qr/toolkit\s*=\s*eumm/,
408             qr/toolkit_hardness\s*=\s*soft/, qr/srcreadme\s*=.*/,
409             qr/copyright_holder\s*=.*<[^@]+@[^>]+>/, qr/twitter_extra_hash_tags\s*=\s*#/,
410             qr/;\s*vim:\s+.*syntax=dosini/,
411             );
412             my (@unwanted_regex) = (
413             #
414             qr/copyfiles\s*=.*LICENSE/,
415             qr/author.*=.*kentfredric/, qr/git_versions/, #
416             qr/twitter_hash_tags\s*=\s*#perl\s+#cpan\s*/, #
417             );
418             my $ok = $file;
419             for my $test (@wanted_regex) {
420             undef $ok unless $assert->should( have_line => $file, $test );
421             }
422             for my $test (@unwanted_regex) {
423             undef $ok unless $assert->should_not( have_line => $file, $test );
424             }
425              
426             _is_bad {
427             undef $ok unless $assert->should( have_one_of_line => $file, qr/bumpversions\s*=\s*1/, qr/git_versions/ );
428             };
429              
430             return $ok;
431             };
432              
433             lsub unrecommend => sub {
434             [
435 5     5   200 qw( Path::Class Path::Class::File Path::Class::Dir ), # Path::Tiny preferred
436             qw( JSON JSON::XS JSON::Any ), # JSON::MaybeXS preferred
437             qw( Path::IsDev Path::FindDev ), # Ugh, this is such a bad idea
438             qw( File::ShareDir::ProjectDistDir ), # Whhhy
439             qw( File::Find File::Find::Rule ), # Path::Iterator::Rule is much better
440             qw( Class::Load ), # Module::Runtime preferred
441             qw( Readonly ), # use Const::Fast
442             qw( Sub::Name ), # use Sub::Util
443             qw( autobox ), # Rewrite it
444             qw( Moose::Autobox ), # Rewrite it
445             qw( List::MoreUtils ), # Some people want to avoid it,
446             # consider avoiding if its easy to do so
447             ];
448             };
449              
450             sub avoid_old_modules {
451 5     5 0 9 my ($self) = @_;
452 5 50       146 return unless my $distmeta = $self->zilla->distmeta;
453 5         397 my $assert = $self->_dc;
454              
455 5         10 my $ok = 1;
456 5         27 for my $bad ( @{ $self->unrecommend } ) {
  5         156  
457 85 50       289 undef $ok unless $assert->should_not( have_dpath => 'distmeta', $distmeta, '/prereqs/*/*/' . $bad );
458             }
459 5         13 return $ok;
460             }
461              
462             _after_true 'mailmap' => sub {
463             my ( $mailmap, $self ) = @_;
464             my $ok = $mailmap;
465             undef $ok unless $self->_pc->should( have_line => $mailmap, qr/<kentnl\@cpan.org>.*<kentfredric\@gmail.com>/ );
466             return $ok;
467             };
468              
469             # Hack to avoid matching ourselves.
470             sub _plugin_re {
471 1     1   2 my $inpn = shift;
472 1         7 my $pn = join q[::], split qr/\+/, $inpn;
473 1         14 return qr/$pn/;
474             }
475              
476             sub dzil_plugin_check {
477 5     5 0 10 my ($self) = @_;
478 5 100       149 return unless $self->libdir;
479 2 100       17 return unless @{ $self->libfiles };
  2         79  
480 1         45 my $assert = $self->_pc;
481 1         2 my (@plugins) = grep { $_->stringify =~ /\Alib\/Dist\/Zilla\/Plugin\//msx } @{ $self->libfiles };
  1         8  
  1         26  
482 1 50       9 return unless @plugins;
483 1         3 for my $plugin (@plugins) {
484 1         3 $assert->should_not( have_line => $plugin, _plugin_re('Dist+Zilla+Util+ConfigDumper') );
485             }
486 1 50       31 return unless $self->tdir;
487 1 50       9 return unless @{ $self->tfiles };
  1         27  
488             FIND_DZTEST: {
489 1         7 for my $tfile ( @{ $self->tfiles } ) {
  1         1  
  1         30  
490 1 50       10 if ( $assert->test( have_line => $tfile, qr/dztest/ ) ) {
491 0         0 $self->log('Tests should probably not use dztest (Dist::Zilla::Util::Test::KENTNL)');
492 0         0 last FIND_DZTEST;
493             }
494             }
495             }
496 1         19 return;
497             }
498              
499             sub setup_installer {
500 5     5 0 323340 my ($self) = @_;
501 5         169 $self->git;
502 5         45 $self->git_config;
503 5         45 $self->dist_ini;
504 5         22 $self->dist_ini_meta;
505 5         40 $self->weaver_ini;
506 5         44 $self->travis_yml;
507 5         46 $self->contributing_pod;
508 5         47 $self->makefile_pl;
509 5         166 $self->perltidyrc;
510 5         42 $self->gitignore;
511 5         166 $self->changes;
512 5         157 $self->license;
513 5         37 $self->has_new_changes_deps;
514 5         23 $self->perlcritic_deps;
515 5         50 $self->perlcritic_gen;
516 5         38 $self->avoid_old_modules;
517 5         27 $self->mailmap;
518 5         43 $self->dzil_plugin_check;
519 5         42 return;
520             }
521              
522             __PACKAGE__->meta->make_immutable;
523 6     6   56 no Moose;
  6         10  
  6         52  
524              
525             1;
526              
527             __END__
528              
529             =pod
530              
531             =encoding UTF-8
532              
533             =head1 NAME
534              
535             Dist::Zilla::Plugin::Author::KENTNL::RecommendFixes - Recommend generic changes to the dist.
536              
537             =head1 VERSION
538              
539             version 0.005003
540              
541             =head1 DESCRIPTION
542              
543             Nothing interesting to see here.
544              
545             This module just informs me during C<dzil build> that a bunch of
546             changes that I intend to make to multiple modules have not been applied
547             to the current distribution.
548              
549             It does this by spewing colored output.
550              
551             =for Pod::Coverage setup_installer
552             has_new_changes_deps
553             avoid_old_modules
554             dzil_plugin_check
555              
556             =head1 AUTHOR
557              
558             Kent Fredric <kentnl@cpan.org>
559              
560             =head1 COPYRIGHT AND LICENSE
561              
562             This software is copyright (c) 2017 by Kent Fredric <kentfredric@gmail.com>.
563              
564             This is free software; you can redistribute it and/or modify it under
565             the same terms as the Perl 5 programming language system itself.
566              
567             =cut