File Coverage

blib/lib/Dist/Zilla/Plugin/Test/Compile/PerFile.pm
Criterion Covered Total %
statement 115 120 95.8
branch 19 26 73.0
condition 2 3 66.6
subroutine 27 27 100.0
pod 1 2 50.0
total 164 178 92.1


line stmt bran cond sub pod time code
1 9     9   17359742 use 5.006; # our
  9         23  
2 9     9   38 use strict;
  9         12  
  9         188  
3 9     9   313 use warnings;
  9         11  
  9         619  
4              
5             package Dist::Zilla::Plugin::Test::Compile::PerFile;
6              
7             our $VERSION = '0.003901'; # TRIAL
8              
9             # ABSTRACT: Create a single .t for each compilable file in a distribution
10              
11             our $AUTHORITY = 'cpan:KENTNL'; # AUTHORITY
12              
13 9     9   553 use Moose qw( with around has );
  9         289284  
  9         63  
14 9     9   35679 use MooseX::LazyRequire;
  9         15486  
  9         68  
15              
16             with 'Dist::Zilla::Role::FileGatherer', 'Dist::Zilla::Role::TextTemplate';
17              
18 9     9   24549 use Path::Tiny qw(path);
  9         7309  
  9         520  
19 9     9   467 use File::ShareDir qw(dist_dir);
  9         4741  
  9         573  
20 9     9   39 use Moose::Util::TypeConstraints qw(enum);
  9         9  
  9         73  
21              
22             ## no critic (ProhibitPackageVars)
23             our %path_translators;
24              
25             $path_translators{base64_filter} = sub {
26             my ($file) = @_;
27             $file =~ s/[^-[:alnum:]_]+/_/msxg;
28             return $file;
29             };
30              
31             $path_translators{mimic_source} = sub {
32             my ($file) = @_;
33             return $file;
34             };
35              
36             ##
37             #
38             # This really example code, because this notation is so unrecommended, as Colons in file names
39             # are highly non-portable.
40             #
41             # Edit this to = 1 if you're 100% serious you want this.
42             #
43             ##
44              
45             if (0) {
46             $path_translators{module_names} = sub {
47             my ($file) = @_;
48             return $file if $file !~ /\Alib\//msx;
49             return $file if $file !~ /[.]pm\z/msx;
50             $file =~ s{\Alib/}{}msx;
51             $file =~ s{[.]pm\z}{}msx;
52             $file =~ s{/}{::}msxg;
53             $file = 'module/' . $file;
54             return $file;
55             };
56             }
57              
58             our %templates = ();
59              
60             {
61             my $dist_dir = dist_dir('Dist-Zilla-Plugin-Test-Compile-PerFile');
62             my $template_dir = path($dist_dir);
63             for my $file ( $template_dir->children ) {
64             next if $file =~ /\A[.]/msx; # Skip hidden files
65             next if -d $file; # Skip directories
66             $templates{ $file->basename } = $file;
67             }
68             }
69              
70             around mvp_multivalue_args => sub {
71             my ( $orig, $self, @args ) = @_;
72             return ( 'finder', 'file', 'skip', $self->$orig(@args) );
73             };
74              
75             around mvp_aliases => sub {
76             my ( $orig, $self, @args ) = @_;
77             my $hash = $self->$orig(@args);
78             $hash = {} if not defined $hash;
79             $hash->{files} = 'file';
80             return $hash;
81             };
82              
83             around dump_config => sub {
84             my ( $orig, $self, @args ) = @_;
85             my $config = $self->$orig(@args);
86             my $localconf = $config->{ +__PACKAGE__ } = {};
87              
88             $localconf->{finder} = $self->finder if $self->has_finder;
89             $localconf->{xt_mode} = $self->xt_mode;
90             $localconf->{prefix} = $self->prefix;
91             $localconf->{file} = [ sort @{ $self->file } ];
92             $localconf->{skip} = $self->skip;
93             $localconf->{path_translator} = $self->path_translator;
94             $localconf->{test_template} = $self->test_template;
95              
96             $localconf->{ q[$] . __PACKAGE__ . '::VERSION' } = $VERSION
97             unless __PACKAGE__ eq ref $self;
98              
99             return $config;
100             };
101              
102              
103              
104              
105              
106              
107              
108              
109              
110             sub BUILD {
111 8     8 0 14 my ($self) = @_;
112 8 100       369 return if $self->has_file;
113 7 100       306 return if $self->has_finder;
114 6         228 $self->_finder_objects;
115 6         189 return;
116             }
117              
118              
119              
120              
121              
122              
123              
124              
125              
126              
127              
128              
129              
130             has xt_mode => ( is => ro =>, isa => Bool =>, lazy_build => 1 );
131              
132              
133              
134              
135              
136              
137              
138              
139              
140              
141              
142              
143              
144             has prefix => ( is => ro =>, isa => Str =>, lazy_build => 1 );
145              
146              
147              
148              
149              
150              
151              
152              
153              
154              
155              
156              
157              
158              
159              
160              
161              
162              
163             has file => ( is => ro =>, isa => 'ArrayRef[Str]', lazy_build => 1, );
164              
165              
166              
167              
168              
169              
170              
171              
172              
173              
174              
175             has skip => ( is => ro =>, isa => 'ArrayRef[Str]', lazy_build => 1, );
176              
177              
178              
179              
180              
181              
182              
183              
184              
185              
186              
187              
188              
189              
190             has finder => ( is => ro =>, isa => 'ArrayRef[Str]', lazy_required => 1, predicate => 'has_finder' );
191              
192              
193              
194              
195              
196              
197              
198              
199              
200              
201              
202              
203              
204              
205              
206              
207              
208              
209              
210              
211              
212              
213              
214              
215              
216              
217              
218              
219              
220              
221              
222              
223              
224              
225              
226              
227              
228              
229              
230              
231              
232              
233              
234              
235              
236              
237              
238              
239              
240              
241              
242              
243              
244              
245              
246              
247              
248              
249              
250              
251              
252              
253              
254              
255              
256              
257              
258             has path_translator => ( is => ro =>, isa => enum( [ sort keys %path_translators ] ), lazy_build => 1 );
259              
260              
261              
262              
263              
264              
265              
266              
267              
268              
269              
270              
271              
272              
273              
274              
275              
276              
277              
278              
279              
280              
281              
282              
283              
284              
285              
286             has test_template => ( is => ro =>, isa => enum( [ sort keys %templates ] ), lazy_build => 1 );
287              
288             sub _generate_file {
289 8     8   18 my ( $self, $name, $file ) = @_;
290 8 50       56 my $relpath = ( $file =~ /\Alib\/(.*)\z/msx ? $1 : q[./] . $file );
291              
292 8         42 $self->log_debug("relpath for $file is: $relpath");
293              
294             my $code = sub {
295 8 50   8   263341 return $self->fill_in_string(
296             $self->_test_template_content,
297             {
298             file => $file,
299             relpath => $relpath,
300             plugin_module => $self->meta->name,
301             plugin_name => $self->plugin_name,
302             plugin_version => ( $self->VERSION ? $self->VERSION : '<self>' ),
303             test_more_version => '0.89',
304             },
305             );
306 8         1697 };
307 8         218 return Dist::Zilla::File::FromCode->new(
308             name => $name,
309             code_return_type => 'text',
310             code => $code,
311             );
312             }
313              
314              
315              
316              
317              
318              
319              
320              
321              
322              
323              
324             sub gather_files {
325 8     8 1 641189 my ($self) = @_;
326 8         4464 require Dist::Zilla::File::FromCode;
327              
328 8         413513 my $prefix = $self->prefix;
329 8         36 $prefix =~ s{/?\z}{/}msx;
330              
331 8         286 my $translator = $self->_path_translator;
332              
333 8 50       11 if ( not @{ $self->file } ) {
  8         280  
334 0         0 $self->log_debug('Did not find any files to add tests for, did you add any files yet?');
335 0         0 return;
336             }
337 8         18 my $skiplist = {};
338 8         12 for my $skip ( @{ $self->skip } ) {
  8         281  
339 1         3 $skiplist->{$skip} = 1;
340             }
341 8         12 for my $file ( @{ $self->file } ) {
  8         253  
342 9 100       33 if ( exists $skiplist->{$file} ) {
343 1         7 $self->log_debug("Skipping compile test generation for $file");
344 1         225 next;
345             }
346 8         30 my $name = sprintf q[%s%s.t], $prefix, $translator->($file);
347 8         54 $self->log_debug("Adding $name for $file");
348 8         2048 $self->add_file( $self->_generate_file( $name, $file ) );
349             }
350 8         5161 return;
351             }
352              
353             has _path_translator => ( is => ro =>, isa => CodeRef =>, lazy_build => 1, init_arg => undef );
354             has _test_template => ( is => ro =>, isa => Defined =>, lazy_build => 1, init_arg => undef );
355             has _test_template_content => ( is => ro =>, isa => Defined =>, lazy_build => 1, init_arg => undef );
356             has _finder_objects => ( is => ro =>, isa => 'ArrayRef', lazy_build => 1, init_arg => undef );
357              
358             sub _build_xt_mode {
359 7     7   215 return;
360             }
361              
362             sub _build_prefix {
363 8     8   14 my ($self) = @_;
364 8 100       309 if ( $self->xt_mode ) {
365 1         32 return 'xt/author/00-compile';
366             }
367 7         1048 return 't/00-compile';
368             }
369              
370             sub _build_path_translator {
371 7     7   15 my ( undef, ) = @_;
372 7         226 return 'base64_filter';
373             }
374              
375             sub _build__path_translator {
376 8     8   15 my ($self) = @_;
377 8         283 my $translator = $self->path_translator;
378 8         268 return $path_translators{$translator};
379             }
380              
381             sub _build_test_template {
382 8     8   253 return '01-basic.t.tpl';
383             }
384              
385             sub _build__test_template {
386 8     8   19 my ($self) = @_;
387 8         287 my $template = $self->test_template;
388 8         282 return $templates{$template};
389             }
390              
391             sub _build__test_template_content {
392 8     8   13 my ($self) = @_;
393 8         289 my $template = $self->_test_template;
394 8         31 return $template->slurp_utf8;
395             }
396              
397             sub _build_file {
398 7     7   13 my ($self) = @_;
399 7         12 return [ map { $_->name } @{ $self->_found_files } ];
  8         52  
  7         36  
400             }
401              
402             sub _build_skip {
403 7     7   238 return [];
404             }
405              
406             sub _build__finder_objects {
407 7     7   12 my ($self) = @_;
408 7 100       271 if ( $self->has_finder ) {
409 1         2 my @out;
410 1         1 for my $finder ( @{ $self->finder } ) {
  1         31  
411 1         24 my $plugin = $self->zilla->plugin_named($finder);
412 1 50       129 if ( not $plugin ) {
413 0         0 $self->log_fatal("no plugin named $finder found");
414             }
415 1 50       4 if ( not $plugin->does('Dist::Zilla::Role::FileFinder') ) {
416 0         0 $self->log_fatal("plugin $finder is not a FileFinder");
417             }
418 1         36 push @out, $plugin;
419             }
420 1         34 return \@out;
421             }
422 6         46 return [ $self->_vivify_installmodules_pm_finder ];
423             }
424              
425             sub _vivify_installmodules_pm_finder {
426 6     6   11 my ($self) = @_;
427 6         222 my $name = $self->plugin_name;
428 6         96 $name .= '/AUTOVIV/:InstallModulesPM';
429 6 50       203 if ( my $plugin = $self->zilla->plugin_named($name) ) {
430 0         0 return $plugin;
431             }
432 6         4909 require Dist::Zilla::Plugin::FinderCode;
433             my $plugin = Dist::Zilla::Plugin::FinderCode->new(
434             {
435             plugin_name => $name,
436             zilla => $self->zilla,
437             style => 'grep',
438             code => sub {
439 13     13   912 my ( $file, $self ) = @_;
440 13         47 local $_ = $file->name;
441             ## no critic (RegularExpressions)
442 13 100 66     597 return 1 if m{\Alib/} and m{\.(pm)$};
443 6 50       136 return 1 if $_ eq $self->zilla->main_module;
444 6         8900 return;
445             },
446             },
447 6         216863 );
448 6         473 push @{ $self->zilla->plugins }, $plugin;
  6         165  
449 6         379 return $plugin;
450             }
451              
452             sub _found_files {
453 7     7   11 my ($self) = @_;
454 7         9 my %by_name;
455 7         11 for my $plugin ( @{ $self->_finder_objects } ) {
  7         243  
456 7         12 for my $file ( @{ $plugin->find_files } ) {
  7         38  
457 8         265 $by_name{ $file->name } = $file;
458             }
459             }
460 7         265 return [ values %by_name ];
461             }
462              
463             __PACKAGE__->meta->make_immutable;
464 9     9   14569 no Moose;
  9         15  
  9         43  
465 9     9   847 no Moose::Util::TypeConstraints;
  9         13  
  9         34  
466              
467             1;
468              
469             __END__
470              
471             =pod
472              
473             =encoding UTF-8
474              
475             =head1 NAME
476              
477             Dist::Zilla::Plugin::Test::Compile::PerFile - Create a single .t for each compilable file in a distribution
478              
479             =head1 VERSION
480              
481             version 0.003901
482              
483             =head1 SYNOPSIS
484              
485             ; in dist.ini
486             [Test::Compile::PerFile]
487              
488             =head1 DESCRIPTION
489              
490             This module is inspired by its earlier sibling L<< C<[Test::Compile]>|Dist::Zilla::Plugin::Test::Compile >>.
491              
492             Test::Compile is awesome, however, in the process of its development, we discovered it might be useful
493             to run compilation tests in parallel.
494              
495             This lead to the realization that implementing said functions are kinda messy.
496              
497             However, a further realization is, that parallelism should not be codified in the test itself, because platform parallelism is
498             rather not very portable, so parallelism should only be enabled when asked for.
499              
500             And this lead to the realization that C<prove> and C<Test::Harness> B<ALREADY> implement parallelism, and B<ALREADY> provide a
501             safe way for platforms to indicate parallelism is wanted.
502              
503             Which means implementing another layer of parallelism is unwanted and unproductive effort ( which may be also filled with messy
504             parallelism-induced bugs )
505              
506             So, here is the Test::Compile model based on how development is currently proceeding.
507              
508             prove
509             \ ----- 00_compile.t
510             | \ ----- Compile Module 1
511             | \ ----- Compile Module 2
512             |
513             \ ----- 01_basic.t
514              
515             That may be fine for some people, but this approach has several fundamental limits:
516              
517             =over 4
518              
519             =item 1. Sub-Tasks of compile don't get load balanced by the master harness.
520              
521             =item 2. Parallelism is developer side, not deployment side governed.
522              
523             =item 3. This approach means C<prove -s> will have no impact.
524              
525             =item 4. This approach means C<prove -j> will have no impact.
526              
527             =item 5. This approach inhibits other features of C<prove> such as the C<--state=slow>
528              
529             =back
530              
531             So this variation aims to employ one test file per module, to leverage C<prove> power.
532              
533             One initial concern cropped up on the notion of having excessive numbers of C<perl> instances, e.g:
534              
535             prove
536             \ ----- 00_compile/01_Module_1.t
537             | \ ----- Compile Module 1
538             |
539             \ ----- 00_compile/02_Module_2.t
540             | \ ----- Compile Module 2
541             |
542             \ ----- 01_basic.t
543              
544             If we were to implement it this way, we'd have the fun overhead of having to spawn B<2> C<perl> instances
545             per module tested, which on C<Win32>, would roughly double the test time and give nothing in return.
546              
547             However, B<Most> of the reason for having a C<perl> process per compile, was to separate the modules from each other
548             to assure they could be loaded independently.
549              
550             So because we already have a basically empty compile-state per test, we can reduce the number of C<perl> processes to as many
551             modules as we have.
552              
553             prove
554             \ ----- 00_compile/01_Module_1.t
555             |
556             \ ----- 00_compile/02_Module_2.t
557             |
558             \ ----- 01_basic.t
559              
560             Granted, there is still some bleed here, because doing it like this means you have some modules preloaded prior to compiling the
561             module in question, namely, that C<Test::*> will be in scope.
562              
563             However, "testing these modules compile without C<Test::> loaded" is not the real purpose of the compile tests,
564             the compile tests are to make sure the modules load.
565              
566             So this is an acceptable caveat for this module, and if you wish to be distinct from C<Test::*>, then you're encouraged to use the
567             much more proven C<[Test::Compile]>.
568              
569             Though we may eventually provide an option to spawn additional C<perl> processes to more closely mimic C<Test::*>'s behaviour,
570             the cost of doing so should not be understated, and as this module exist to attempt to improve efficiency of tests, not to
571             decrease them, that would be an approach counter-productive to this modules purpose.
572              
573             =head1 METHODS
574              
575             =head2 C<gather_files>
576              
577             This plugin operates B<ONLY> during C<gather_files>, unlike other plugins which have multiple phase involvement, this only
578             happens at this phase.
579              
580             The intrinsic dependence of this plugin on other files in your dist, means that in order for it to generate a test for any given
581             file, the test itself must be included B<after> that file is gathered.
582              
583             =head1 ATTRIBUTES
584              
585             =head2 C<xt_mode>
586              
587             I<optional> B<< C<Bool> >>
588              
589             xt_mode = 1
590              
591             If set, C<prefix> defaults to C<xt/author/00-compile>
592              
593             I<Default> is B<NOT SET>
594              
595             =head2 C<prefix>
596              
597             I<optional> B<< C<Str> >>
598              
599             prefix = t/99-compilerthingys
600              
601             If set, sets the prefix path for generated tests to go in.
602              
603             I<Defaults> to C<t/00-compile>
604              
605             =head2 C<file>
606              
607             I<optional> B<< C<multivalue_arg> >> B<< C<ArrayRef[Str]> >>
608              
609             B<< C<mvp_aliases> >>: C<files>
610              
611             file = lib/Foo.pm
612             file = lib/Bar.pm
613             files = lib/Quux.pm
614             file = script/whatever.pl
615              
616             Specifies the list of source files to generate compile tests for.
617              
618             I<If not specified>, defaults are populated from the file finder C<finder>
619              
620             =head2 C<skip>
621              
622             I<optional> B<< C<multivalue_arg> >> B<< C<ArrayRef[Str]> >>
623              
624             skip = lib/Foo.pm
625              
626             Specifies the list of source files to skip compile tests for.
627              
628             =head2 C<finder>
629              
630             I<optional> B<< C<multivalue_arg> >> B<< C<ArrayRef[Str]> >>
631              
632             finder = :InstallModules
633              
634             Specifies a L<< C<FileFinder>|Dist::Zilla::Role::FileFinder >> plugin name
635             to query for a list of files to build compile tests for.
636              
637             I<If not specified>, a custom one is autovivified, and matches only C<*.pm> in C<lib/>
638              
639             =head2 C<path_translator>
640              
641             I<optional> B<< C<Str> >>
642              
643             A Name of a routine to translate source paths ( i.e: Paths to modules/scripts that are to be compiled )
644             into test file names.
645              
646             I<Default> is C<base64_filter>
647              
648             Supported Values:
649              
650             =over 4
651              
652             =item * C<base64_filter>
653              
654             Paths are L<< mangled so that they contain only base64 web-safe elements|http://tools.ietf.org/html/rfc3548#section-4 >>
655              
656             That is to say, if you were building tests for a distribution with this layout:
657              
658             lib/Foo/Bar.pm
659             lib/Foo.pm
660             lib/Foo_Quux.pm
661              
662             That the generated test files will be in the C<prefix> directory named:
663              
664             lib_Foo_Bar_pm.t
665             lib_Foo_pm.t
666             lib_Foo_Quux.t
667              
668             This is the default, but not necessarily the most sane if you have unusual file naming.
669              
670             lib/Foo/Bar.pm
671             lib/Foo_Bar.pm
672              
673             This configuration will not work with this translator.
674              
675             =item * C<mimic_source>
676              
677             This is mostly a 1:1 mapping, it doesn't translate source names in any way, other than prefixing and suffixing,
678             which is standard regardless of translation chosen.
679              
680             lib/Foo/Bar.pm
681             lib/Foo.pm
682             lib/Foo_Quux.pm
683              
684             Will emit a prefix directory populated as such
685              
686             lib/Foo/Bar.pm.t
687             lib/Foo.pm.t
688             lib/Foo_Quux.pm.t
689              
690             Indeed, if you had a death wish, you could set C<prefix = lib> and your final layout would be:
691              
692             lib/Foo/Bar.pm
693             lib/Foo/Bar.pm.t
694             lib/Foo.pm
695             lib/Foo.pm.t
696             lib/Foo_Quux.pm
697             lib/Foo_Quux.pm.t
698              
699             Though this is not advised, and is only given for an example.
700              
701             =back
702              
703             =head2 C<test_template>
704              
705             Contains the string of the template file you wish to use as a reference point.
706              
707             Unlike most plugins, which use L<< C<Data::Section>|Data::Section >> to provide their templates,
708             this plugin uses a L<< C<File::ShareDir> C<dist_dir>|File::ShareDir >> to distribute templates.
709              
710             This means there will always be a predetermined list of templates shipped by this plugin,
711             however, if you wish to modify these templates and store them with a non-colliding name, for your personal convenience,
712             you are entirely free to so.
713              
714             As such, this field takes as its parameter, the name of any file that happened to be in the C<dist_dir> at compile time.
715              
716             Provided Templates:
717              
718             =over 4
719              
720             =item * C<01-basic.t.tpl>
721              
722             A very basic standard template, which C<use>'s C<Test::More>, does a C<requires_ok($file)> for the requested file, and nothing
723             else.
724              
725             =back
726              
727             =for Pod::Coverage BUILD
728              
729             =head1 Other Important Differences to Test::Compile
730              
731             =head2 Finders useful, but not required
732              
733             C<[Test::Compile::PerFile]> supports providing an arbitrary list of files to generate compile tests
734              
735             [Test::Compile::PerFile]
736             file = lib/Foo.pm
737             file = lib/Quux.pm
738              
739             Using this will supersede using finders to find things.
740              
741             =head2 Single finder only, not multiple
742              
743             C<[Test::Compile]> supports 2 finder keys, C<module_finder> and C<script_finder>.
744              
745             This module only supports one key, C<finder>, and it is expected
746             that if you want to test 2 different sets of files, you'll create a separate instance for that:
747              
748             -[Test::Compile]
749             -module_finder = Foo
750             -script_finder = bar
751             +[Test::Compile::PerFile / module compile tests]
752             +finder = Foo
753             +[Test::Compile::PerFile / script compile tests]
754             +finder = bar
755              
756             This is harder to do with C<[Test::Compile]>, because you'd have to declare a separate file name for it to work,
757             where-as C<[Test::Compile::PerFile]> generates a unique file name for each source it tests.
758              
759             Collisions are still possible, but harder to hit by accident.
760              
761             =head2 File Oriented, not Module Oriented
762              
763             Under the hood, C<Test::Compile> is really file oriented too, it just doesn't give that impression on the box.
764              
765             It just seemed fundamentally less complex to deal only in file paths for this module, as it gives
766             no illusions as to what it can, and cannot do.
767              
768             ( For example, by being clearly file oriented, there's no ambiguity of how it will behave when a file name and a module name are
769             miss-matching in some way, by simply not caring about the latter , it will also never attempt to probe and load modules that can't
770             be automatically resolved to files )
771              
772             =head1 Performance
773              
774             A rough comparison on the C<dzil> git tree, with C<HARNESS_OPTIONS=j4:c> where C<4> is the number of logical C<CPUs> I have:
775              
776             Test::Compile - Files= 42, Tests=577, 57 wallclock secs ( 0.32 usr 0.11 sys + 109.29 cusr 11.13 csys = 120.85 CPU)
777             Test::Compile::PerFile - Files=176, Tests=576, 44 wallclock secs ( 0.83 usr 0.39 sys + 127.34 cusr 13.27 csys = 141.83 CPU)
778              
779             So a 20% saving for a 300% growth in file count, a 500k growth in unpacked tar size, and a 4k growth in C<tar.gz> size.
780              
781             Hmm, that's a pretty serious trade off. Might not really be worth the savings.
782              
783             Though, comparing compile tests alone:
784              
785             # Test::Compile
786             prove -j4lr --timer t/00-compile.t
787             Files=1, Tests=135, 41 wallclock secs ( 0.07 usr 0.01 sys + 36.82 cusr 3.58 csys = 40.48 CPU)
788              
789             # Test::Compile::PerFile
790             prove -j4lr --timer t/00-compile/
791             Files=135, Tests=135, 22 wallclock secs ( 0.58 usr 0.32 sys + 64.45 cusr 6.74 csys = 72.09 CPU)
792              
793             That's not bad, considering that although I have 4 logical C<CPUs>, that's really just 2 physical C<CPUs> with hyper-threading ;)
794              
795             =head1 AUTHOR
796              
797             Kent Fredric <kentnl@cpan.org>
798              
799             =head1 COPYRIGHT AND LICENSE
800              
801             This software is copyright (c) 2017 by Kent Fredric <kentfredric@gmail.com>.
802              
803             This is free software; you can redistribute it and/or modify it under
804             the same terms as the Perl 5 programming language system itself.
805              
806             =cut