File Coverage

blib/lib/Dist/Zilla/Plugin/Author/KENTNL/RecommendFixes.pm
Criterion Covered Total %
statement 197 216 91.2
branch 39 64 60.9
condition n/a
subroutine 66 85 77.6
pod 0 4 0.0
total 302 369 81.8


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