1package Test2::Mock;
2use strict;
3use warnings;
4
5our $VERSION = '0.000162';
6
7use Carp qw/croak confess/;
8our @CARP_NOT = (__PACKAGE__);
9
10use Scalar::Util qw/weaken reftype blessed set_prototype/;
11use Test2::Util qw/pkg_to_file/;
12use Test2::Util::Stash qw/parse_symbol slot_to_sig get_symbol get_stash purge_symbol/;
13use Test2::Util::Sub qw/gen_accessor gen_reader gen_writer/;
14
15sub new; # Prevent hashbase from giving us 'new';
16use Test2::Util::HashBase qw/class parent child _purge_on_destroy _blocked_load _symbols _track sub_tracking call_tracking/;
17
18sub new {
19    my $class = shift;
20
21    croak "Called new() on a blessed instance, did you mean to call \$control->class->new()?"
22        if blessed($class);
23
24    my $self = bless({}, $class);
25
26    $self->{+SUB_TRACKING}  ||= {};
27    $self->{+CALL_TRACKING} ||= [];
28
29    my @sets;
30    while (my $arg = shift @_) {
31        my $val = shift @_;
32
33        if ($class->can(uc($arg))) {
34            $self->{$arg} = $val;
35            next;
36        }
37
38        push @sets => [$arg, $val];
39    }
40
41    croak "The 'class' field is required"
42        unless $self->{+CLASS};
43
44    for my $set (@sets) {
45        my ($meth, $val) = @$set;
46        my $type = reftype($val);
47
48        confess "'$meth' is not a valid constructor argument for $class"
49            unless $self->can($meth);
50
51        if (!$type) {
52            $self->$meth($val);
53        }
54        elsif($type eq 'HASH') {
55            $self->$meth(%$val);
56        }
57        elsif($type eq 'ARRAY') {
58            $self->$meth(@$val);
59        }
60        else {
61            croak "'$val' is not a valid argument for '$meth'"
62        }
63    }
64
65    return $self;
66}
67
68sub _check {
69    return unless $_[0]->{+CHILD};
70    croak "There is an active child controller, cannot proceed";
71}
72
73sub purge_on_destroy {
74    my $self = shift;
75    ($self->{+_PURGE_ON_DESTROY}) = @_ if @_;
76    return $self->{+_PURGE_ON_DESTROY};
77}
78
79sub stash {
80    my $self = shift;
81    get_stash($self->{+CLASS});
82}
83
84sub file {
85    my $self = shift;
86    my $file = $self->class;
87    return pkg_to_file($self->class);
88}
89
90sub block_load {
91    my $self = shift;
92    $self->_check();
93
94    my $file = $self->file;
95
96    croak "Cannot block the loading of module '" . $self->class . "', already loaded in file $INC{$file}"
97        if $INC{$file};
98
99    $INC{$file} = __FILE__;
100
101    $self->{+_BLOCKED_LOAD} = 1;
102}
103
104my %NEW = (
105    hash => sub {
106        my ($class, %params) = @_;
107        return bless \%params, $class;
108    },
109    array => sub {
110        my ($class, @params) = @_;
111        return bless \@params, $class;
112    },
113    ref => sub {
114        my ($class, $params) = @_;
115        return bless $params, $class;
116    },
117    ref_copy => sub {
118        my ($class, $params) = @_;
119        my $type = reftype($params);
120
121        return bless {%$params}, $class
122            if $type eq 'HASH';
123
124        return bless [@$params], $class
125            if $type eq 'ARRAY';
126
127        croak "Not sure how to construct an '$class' from '$params'";
128    },
129);
130
131sub override_constructor {
132    my $self = shift;
133    my ($name, $type) = @_;
134    $self->_check();
135
136    my $sub = $NEW{$type}
137        || croak "'$type' is not a known constructor type";
138
139    $self->override($name => $sub);
140}
141
142sub add_constructor {
143    my $self = shift;
144    my ($name, $type) = @_;
145    $self->_check();
146
147    my $sub = $NEW{$type}
148        || croak "'$type' is not a known constructor type";
149
150    $self->add($name => $sub);
151}
152
153sub autoload {
154    my $self = shift;
155    $self->_check();
156    my $class = $self->class;
157    my $stash = $self->stash;
158
159    croak "Class '$class' already has an AUTOLOAD"
160        if $stash->{AUTOLOAD} && *{$stash->{AUTOLOAD}}{CODE};
161    croak "Class '$class' already has an can"
162        if $stash->{can} && *{$stash->{can}}{CODE};
163
164    # Weaken this reference so that AUTOLOAD does not prevent its own
165    # destruction.
166    weaken(my $c = $self);
167
168    my ($file, $line) = (__FILE__, __LINE__ + 3);
169    my $autoload = eval <<EOT || die "Failed generating AUTOLOAD sub: $@";
170package $class;
171#line $line "$file (Generated AUTOLOAD)"
172our \$AUTOLOAD;
173    sub {
174        my (\$self) = \@_;
175        my (\$pkg, \$name) = (\$AUTOLOAD =~ m/^(.*)::([^:]+)\$/g);
176        \$AUTOLOAD = undef;
177
178        return if \$name eq 'DESTROY';
179        my \$sub = sub {
180            my \$self = shift;
181            (\$self->{\$name}) = \@_ if \@_;
182            return \$self->{\$name};
183        };
184
185        \$c->add(\$name => \$sub);
186
187        if (\$c->{_track}) {
188            my \$call = {sub_name => \$name, sub_ref => \$sub, args => [\@_]};
189            push \@{\$c->{sub_tracking}->{\$name}} => \$call;
190            push \@{\$c->{call_tracking}} => \$call;
191        }
192
193        goto &\$sub;
194    }
195EOT
196
197    $line = __LINE__ + 3;
198    my $can = eval <<EOT || die "Failed generating can method: $@";
199package $class;
200#line $line "$file (Generated can)"
201use Scalar::Util 'reftype';
202    sub {
203        my (\$self, \$meth) = \@_;
204        if (\$self->SUPER::can(\$meth)) {
205            return \$self->SUPER::can(\$meth);
206        }
207        elsif (ref \$self && reftype \$self eq 'HASH' && exists \$self->{\$meth}) {
208            return sub { shift->\$meth(\@_) };
209        }
210        return undef;
211    }
212EOT
213
214    {
215        local $self->{+_TRACK} = 0;
216        $self->add(AUTOLOAD => $autoload);
217        $self->add(can => $can);
218    }
219}
220
221sub before {
222    my $self = shift;
223    my ($name, $sub) = @_;
224    $self->_check();
225    my $orig = $self->current($name, required => 1);
226    $self->_inject({}, $name => set_prototype(sub { $sub->(@_); $orig->(@_) }, prototype $sub));
227}
228
229sub after {
230    my $self = shift;
231    my ($name, $sub) = @_;
232    $self->_check();
233    my $orig = $self->current($name, required => 1);
234    $self->_inject(
235        {},
236        $name => set_prototype(
237            sub {
238                my @out;
239
240                my $want = wantarray;
241
242                if ($want) {
243                    @out = $orig->(@_);
244                }
245                elsif (defined $want) {
246                    $out[0] = $orig->(@_);
247                }
248                else {
249                    $orig->(@_);
250                }
251
252                $sub->(@_);
253
254                return @out    if $want;
255                return $out[0] if defined $want;
256                return;
257            },
258            prototype $sub,
259        )
260    );
261}
262
263sub around {
264    my $self = shift;
265    my ($name, $sub) = @_;
266    $self->_check();
267    my $orig = $self->current($name, required => 1);
268    $self->_inject({}, $name => set_prototype(sub { $sub->($orig, @_) }, prototype $sub));
269}
270
271sub add {
272    my $self = shift;
273    $self->_check();
274    $self->_inject({add => 1}, @_);
275}
276
277sub override {
278    my $self = shift;
279    $self->_check();
280    $self->_inject({}, @_);
281}
282
283sub set {
284    my $self = shift;
285    $self->_check();
286    $self->_inject({set => 1}, @_);
287}
288
289sub current {
290    my $self = shift;
291    my ($sym, %params) = @_;
292
293    my $out = get_symbol($sym, $self->{+CLASS});
294    return $out unless $params{required};
295    confess "Attempt to modify a sub that does not exist '$self->{+CLASS}\::$sym' (Mock operates on packages, not classes, are you looking for a symbol in a parent class?)"
296        unless $out;
297    return $out;
298}
299
300sub orig {
301    my $self = shift;
302    my ($sym) = @_;
303
304    $sym = "&$sym" unless $sym =~ m/^[&\$\%\@]/;
305
306    my $syms = $self->{+_SYMBOLS}
307        or croak "No symbols have been mocked yet";
308
309    my $ref = $syms->{$sym};
310
311    croak "Symbol '$sym' is not mocked"
312        unless $ref && @$ref;
313
314    my ($orig) = @$ref;
315
316    return $orig;
317}
318
319sub track {
320    my $self = shift;
321
322    ($self->{+_TRACK}) = @_ if @_;
323
324    return $self->{+_TRACK};
325}
326
327sub clear_call_tracking { @{shift->{+CALL_TRACKING}} = () }
328
329sub clear_sub_tracking {
330    my $self = shift;
331
332    unless (@_) {
333        %{$self->{+SUB_TRACKING}} = ();
334        return;
335    }
336
337    for my $item (@_) {
338        delete $self->{+SUB_TRACKING}->{$item};
339    }
340
341    return;
342}
343
344sub _parse_inject {
345    my $self = shift;
346    my ($param, $arg) = @_;
347
348    if ($param =~ m/^-(.*)$/) {
349        my $sym = $1;
350        my $sig = slot_to_sig(reftype($arg));
351        my $ref = $arg;
352        return ($sig, $sym, $ref);
353    }
354
355    return ('&', $param, $arg)
356        if ref($arg) && reftype($arg) eq 'CODE';
357
358    my ($is, $field, $val);
359
360    if(defined($arg) && !ref($arg) && $arg =~ m/^(rw|ro|wo)$/) {
361        $is    = $arg;
362        $field = $param;
363    }
364    elsif (!ref($arg)) {
365        $val = $arg;
366        $is  = 'val';
367    }
368    elsif (reftype($arg) eq 'HASH') {
369        $field = delete $arg->{field} || $param;
370
371        $val = delete $arg->{val};
372        $is  = delete $arg->{is};
373
374        croak "Cannot specify 'is' and 'val' together" if $val && $is;
375
376        $is ||= $val ? 'val' : 'rw';
377
378        croak "The following keys are not valid when defining a mocked sub with a hashref: " . join(", " => keys %$arg)
379            if keys %$arg;
380    }
381    else {
382        confess "'$arg' is not a valid argument when defining a mocked sub";
383    }
384
385    my $sub;
386    if ($is eq 'rw') {
387        $sub = gen_accessor($field);
388    }
389    elsif ($is eq 'ro') {
390        $sub = gen_reader($field);
391    }
392    elsif ($is eq 'wo') {
393        $sub = gen_writer($field);
394    }
395    else { # val
396        $sub = sub { $val };
397    }
398
399    return ('&', $param, $sub);
400}
401
402sub _inject {
403    my $self = shift;
404    my ($params, @pairs) = @_;
405
406    my $add = $params->{add};
407    my $set = $params->{set};
408
409    my $class = $self->{+CLASS};
410
411    $self->{+_SYMBOLS} ||= {};
412    my $syms = $self->{+_SYMBOLS};
413
414    while (my $param = shift @pairs) {
415        my $arg = shift @pairs;
416        my ($sig, $sym, $ref) = $self->_parse_inject($param, $arg);
417        my $orig = $self->current("$sig$sym");
418
419        croak "Cannot override '$sig$class\::$sym', symbol is not already defined"
420            unless $orig || $add || $set || ($sig eq '&' && $class->can($sym));
421
422        # Cannot be too sure about scalars in globs
423        croak "Cannot add '$sig$class\::$sym', symbol is already defined"
424            if $add && $orig
425            && (reftype($orig) ne 'SCALAR' || defined($$orig));
426
427        $syms->{"$sig$sym"} ||= [];
428        push @{$syms->{"$sig$sym"}} => $orig; # Might be undef, thats expected
429
430        if ($self->{+_TRACK} && $sig eq '&') {
431            my $sub_tracker  = $self->{+SUB_TRACKING};
432            my $call_tracker = $self->{+CALL_TRACKING};
433            my $sub = $ref;
434            $ref = sub {
435                my $call = {sub_name => $sym, sub_ref => $sub, args => [@_]};
436                push @{$sub_tracker->{$param}} => $call;
437                push @$call_tracker => $call;
438                goto &$sub;
439            };
440        }
441
442        no strict 'refs';
443        no warnings 'redefine';
444        *{"$class\::$sym"} = $ref;
445    }
446
447    return;
448}
449
450sub _set_or_unset {
451    my $self = shift;
452    my ($symbol, $set) = @_;
453
454    my $class = $self->{+CLASS};
455
456    return purge_symbol($symbol, $class)
457        unless $set;
458
459    my $sym = parse_symbol($symbol, $class);
460    no strict 'refs';
461    no warnings 'redefine';
462    *{"$class\::$sym->{name}"} = $set;
463}
464
465sub restore {
466    my $self = shift;
467    my ($sym) = @_;
468    $self->_check();
469
470    $sym = "&$sym" unless $sym =~ m/^[&\$\%\@]/;
471
472    my $syms = $self->{+_SYMBOLS}
473        or croak "No symbols are mocked";
474
475    my $ref = $syms->{$sym};
476
477    croak "Symbol '$sym' is not mocked"
478        unless $ref && @$ref;
479
480    my $old = pop @$ref;
481    delete $syms->{$sym} unless @$ref;
482
483    return $self->_set_or_unset($sym, $old);
484}
485
486sub reset {
487    my $self = shift;
488    my ($sym) = @_;
489    $self->_check();
490
491    $sym = "&$sym" unless $sym =~ m/^[&\$\%\@]/;
492
493    my $syms = $self->{+_SYMBOLS}
494        or croak "No symbols are mocked";
495
496    my $ref = delete $syms->{$sym};
497
498    croak "Symbol '$sym' is not mocked"
499        unless $ref && @$ref;
500
501    my ($old) = @$ref;
502
503    return $self->_set_or_unset($sym, $old);
504}
505
506sub reset_all {
507    my $self = shift;
508    $self->_check();
509
510    my $syms = $self->{+_SYMBOLS} || return;
511
512    $self->reset($_) for keys %$syms;
513
514    delete $self->{+_SYMBOLS};
515}
516
517sub _purge {
518    my $self = shift;
519    my $stash = $self->stash;
520    delete $stash->{$_} for keys %$stash;
521}
522
523sub DESTROY {
524    my $self = shift;
525
526    delete $self->{+CHILD};
527    $self->reset_all if $self->{+_SYMBOLS};
528
529    delete $INC{$self->file} if $self->{+_BLOCKED_LOAD};
530
531    $self->_purge if $self->{+_PURGE_ON_DESTROY};
532}
533
5341;
535
536__END__
537
538=pod
539
540=encoding UTF-8
541
542=head1 NAME
543
544Test2::Mock - Module for managing mocked classes and instances.
545
546=head1 DESCRIPTION
547
548This module lets you add and override methods for any package temporarily. When
549the instance is destroyed it will restore the package to its original state.
550
551=head1 SYNOPSIS
552
553    use Test2::Mock;
554    use MyClass;
555
556    my $mock = Test2::Mock->new(
557        track => $BOOL, # enable call tracking if desired
558        class => 'MyClass',
559        override => [
560            name => sub { 'fred' },
561            ...
562        ],
563        add => [
564            is_mocked => sub { 1 }
565            ...
566        ],
567        ...
568    );
569
570    # Unmock the 'name' sub
571    $mock->restore('name');
572
573    ...
574
575    $mock = undef; # Will remove all the mocking
576
577=head1 CONSTRUCTION
578
579=head1 METHODS
580
581=over 4
582
583=item $mock = Test2::Mock->new(class => $CLASS, ...)
584
585This will create a new instance of L<Test2::Mock> that manages mocking
586for the specified C<$CLASS>.
587
588Any C<Test2::Mock> method can be used as a constructor argument, each
589should be followed by an arrayref of arguments to be used within the method. For
590instance the C<add()> method:
591
592    my $mock = Test2::Mock->new(
593        class => 'AClass',
594        add => [foo => sub { 'foo' }],
595    );
596
597is identical to this:
598
599    my $mock = Test2::Mock->new(
600        class => 'AClass',
601    );
602    $mock->add(foo => sub { 'foo' });
603
604=item $mock->track($bool)
605
606Turn tracking on or off. Any sub added/overridden/set when tracking is on will
607log every call in a hash retrievable via C<< $mock->tracking >>. Changing the
608tracking toggle will not affect subs already altered, but will affect any
609additional alterations.
610
611=item $hashref = $mock->sub_tracking
612
613The tracking data looks like this:
614
615    {
616        sub_name => [
617            {sub_name => $sub_name, sub_ref => $mock_subref, args => [... copy of @_ from the call ... ]},
618            ...,
619            ...,
620        ],
621    }
622
623Unlike call_tracking, this lists all calls by sub, so you can choose to only
624look at the sub specific calls.
625
626B<Please note:> The hashref items with the subname and args are shared with
627call_tracking, modifying one modifies the other, so copy first!
628
629=item $arrayref = $mock->call_tracking
630
631The tracking data looks like this:
632
633    [
634        {sub_name => $sub_name, sub_ref => $mock_subref, args => [... copy of @_ from the call ... ]},
635        ...,
636        ...,
637    ]
638
639Unlike sub_tracking this lists all calls to any mocked sub, in the order they
640were called. To filter by sub use sub_tracking.
641
642B<Please note:> The hashref items with the subname and args are shared with
643sub_tracking, modifying one modifies the other, so copy first!
644
645=item $mock->clear_sub_tracking()
646
647=item $mock->clear_sub_tracking(\@subnames)
648
649Clear tracking data. With no arguments ALL tracking data is cleared. When
650arguments are provided then only those specific keys will be cleared.
651
652=item $mock->clear_call_tracking()
653
654Clear all items from call_tracking.
655
656=item $mock->add('symbol' => ..., 'symbol2' => ...)
657
658=item $mock->override('symbol1' => ..., 'symbol2' => ...)
659
660=item $mock->set('symbol1' => ..., 'symbol2' => ...)
661
662C<add()> and C<override()> are the primary ways to add/modify methods for a
663class. Both accept the exact same type of arguments. The difference is that
664C<override> will fail unless the symbol you are overriding already exists,
665C<add> on the other hand will fail if the symbol does already exist.
666
667C<set()> was more recently added for cases where you may not know if the sub
668already exists. These cases are rare, and set should be avoided (think of it
669like 'no strict'). However there are valid use cases, so it was added.
670
671B<Note:> Think of override as a push operation. If you call override on the
672same symbol multiple times it will track that. You can use C<restore()> as a
673pop operation to go back to the previous mock. C<reset> can be used to remove
674all the mocking for a symbol.
675
676Arguments must be a symbol name, with optional sigil, followed by a new
677specification of the symbol. If no sigil is specified then '&' (sub) is
678assumed. A simple example of overriding a sub:
679
680    $mock->override(foo => sub { 'overridden foo' });
681    my $val = $class->foo; # Runs our override
682    # $val is now set to 'overridden foo'
683
684You can also simply provide a value and it will be wrapped in a sub for you:
685
686    $mock->override( foo => 'foo' );
687
688The example above will generate a sub that always returns the string 'foo'.
689
690There are three *special* values that can be used to generate accessors:
691
692    $mock->add(
693        name => 'rw',   # Generates a read/write accessor
694        age  => 'ro',   # Generates a read only accessor
695        size => 'wo',   # Generates a write only accessor
696    );
697
698If you want to have a sub that actually returns one of the three special strings, or
699that returns a coderef, you can use a hashref as the spec:
700
701    my $ref = sub { 'my sub' };
702    $mock->add(
703        rw_string => { val => 'rw' },
704        ro_string => { val => 'ro' },
705        wo_string => { val => 'wo' },
706        coderef   => { val => $ref }, # the coderef method returns $ref each time
707    );
708
709You can also override/add other symbol types, such as hash:
710
711    package Foo;
712    ...
713
714    $mock->add('%foo' => {a => 1});
715
716    print $Foo::foo{a}; # prints '1'
717
718You can also tell mock to deduce the symbol type for the add/override from the
719reference, rules are similar to glob assignments:
720
721    $mock->add(
722        -foo => sub { 'foo' },     # Adds the &foo sub to the package
723        -foo => { foo => 1 },      # Adds the %foo hash to the package
724        -foo => [ 'f', 'o', 'o' ], # Adds the @foo array to the package
725        -foo => \"foo",            # Adds the $foo scalar to the package
726    );
727
728=item $mock->restore($SYMBOL)
729
730Restore the symbol to what it was before the last override. If the symbol was
731recently added this will remove it. If the symbol has been overridden multiple
732times this will ONLY restore it to the previous state. Think of C<override> as a
733push operation, and C<restore> as the pop operation.
734
735=item $mock->reset($SYMBOL)
736
737Remove all mocking of the symbol and restore the original symbol. If the symbol
738was initially added then it will be completely removed.
739
740=item $mock->orig($SYMBOL)
741
742This will return the original symbol, before any mocking. For symbols that were
743added this will return undef.
744
745=item $mock->current($SYMBOL)
746
747This will return the current symbol.
748
749=item $mock->reset_all
750
751Remove all added symbols, and restore all overridden symbols to their originals.
752
753=item $mock->add_constructor($NAME => $TYPE)
754
755=item $mock->override_constructor($NAME => $TYPE)
756
757This can be used to inject constructors. The first argument should be the name
758of the constructor. The second argument specifies the constructor type.
759
760The C<hash> type is the most common, all arguments are used to create a new
761hash that is blessed.
762
763    hash => sub  {
764        my ($class, %params) = @_;
765        return bless \%params, $class;
766    };
767
768The C<array> type is similar to the hash type, but accepts a list instead of
769key/value pairs:
770
771    array => sub {
772        my ($class, @params) = @_;
773        return bless \@params, $class;
774    };
775
776The C<ref> type takes a reference and blesses it. This will modify your
777original input argument.
778
779    ref => sub {
780        my ($class, $params) = @_;
781        return bless $params, $class;
782    };
783
784The C<ref_copy> type will copy your reference and bless the copy:
785
786    ref_copy => sub {
787        my ($class, $params) = @_;
788        my $type = reftype($params);
789
790        return bless {%$params}, $class
791            if $type eq 'HASH';
792
793        return bless [@$params], $class
794            if $type eq 'ARRAY';
795
796        croak "Not sure how to construct a '$class' from '$params'";
797    };
798
799=item $mock->before($NAME, sub { ... })
800
801This will replace the original sub C<$NAME> with a new sub that calls your
802custom code just before calling the original method. The return from your
803custom sub is ignored. Your sub and the original both get the unmodified
804arguments.
805
806=item $mock->after($NAME, sub { ... })
807
808This is similar to before, except your callback runs after the original code.
809The return from your callback is ignored.
810
811=item $mock->around($NAME, sub { ... })
812
813This gives you the chance to wrap the original sub:
814
815    $mock->around(foo => sub {
816        my $orig = shift;
817        my $self = shift;
818        my (@args) = @_;
819
820        ...
821        $self->$orig(@args);
822        ...
823
824        return ...;
825    });
826
827The original sub is passed in as the first argument, even before C<$self>. You
828are responsible for making sure your wrapper sub returns the correct thing.
829
830=item $mock->autoload
831
832This will inject an C<AUTOLOAD> sub into the class. This autoload will
833automatically generate read-write accessors for any sub called that does not
834already exist.
835
836=item $mock->block_load
837
838This will prevent the real class from loading until the mock is destroyed. This
839will fail if the class is already loaded. This will let you mock a class
840completely without loading the original module.
841
842=item $pm_file = $mock->file
843
844This returns the relative path to the file for the module. This corresponds to
845the C<%INC> entry.
846
847=item $bool = $mock->purge_on_destroy($bool)
848
849When true, this will cause the package stash to be completely obliterated when
850the mock object falls out of scope or is otherwise destroyed. You do not
851normally want this.
852
853=item $stash = $mock->stash
854
855This returns the stash for the class being mocked. This is the equivalent of:
856
857    my $stash = \%{"${class}\::"};
858
859This saves you from needing to turn off strict.
860
861=item $class = $mock->class
862
863The class being mocked by this instance.
864
865=item $p = $mock->parent
866
867If you mock a class twice the first instance is the parent, the second is the
868child. This prevents the parent from being destroyed before the child, which
869would lead to a very unpleasant situation.
870
871=item $c = $mock->child
872
873Returns the child mock, if any.
874
875=back
876
877=head1 SOURCE
878
879The source code repository for Test2-Suite can be found at
880L<https://github.com/Test-More/Test2-Suite/>.
881
882=head1 MAINTAINERS
883
884=over 4
885
886=item Chad Granum E<lt>exodist@cpan.orgE<gt>
887
888=back
889
890=head1 AUTHORS
891
892=over 4
893
894=item Chad Granum E<lt>exodist@cpan.orgE<gt>
895
896=back
897
898=head1 COPYRIGHT
899
900Copyright 2018 Chad Granum E<lt>exodist@cpan.orgE<gt>.
901
902This program is free software; you can redistribute it and/or
903modify it under the same terms as Perl itself.
904
905See L<https://dev.perl.org/licenses/>
906
907=cut
908