File Coverage

blib/lib/Type/Tiny/Enum.pm
Criterion Covered Total %
statement 210 222 95.5
branch 80 110 72.7
condition 27 41 65.8
subroutine 43 44 97.7
pod 17 17 100.0
total 377 434 87.3


line stmt bran cond sub pod time code
1             package Type::Tiny::Enum;
2              
3 36     26   71060 use 5.008001;
  26         121  
4 26     26   158 use strict;
  26         55  
  26         592  
5 26     26   124 use warnings;
  26         66  
  26         1163  
6              
7             BEGIN {
8 26     26   107 $Type::Tiny::Enum::AUTHORITY = 'cpan:TOBYINK';
9 26         2204 $Type::Tiny::Enum::VERSION = '2.003_000';
10             }
11              
12             $Type::Tiny::Enum::VERSION =~ tr/_//d;
13              
14 5     5   28 sub _croak ($;@) { require Error::TypeTiny; goto \&Error::TypeTiny::croak }
  5         22  
15              
16 26     26   1297 use Exporter::Tiny 1.004001 ();
  26         9740  
  26         571  
17 26     26   1618 use Type::Tiny ();
  26         81  
  26         8192  
18             our @ISA = qw( Type::Tiny Exporter::Tiny );
19              
20             __PACKAGE__->_install_overloads(
21 12     12   64 q[@{}] => sub { shift->values },
22             );
23              
24             sub _exporter_fail {
25 1     1   175 my ( $class, $type_name, $values, $globals ) = @_;
26 1         6 my $caller = $globals->{into};
27 1         12 my $type = $class->new(
28             name => $type_name,
29             values => [ @$values ],
30             coercion => 1,
31             );
32             $INC{'Type/Registry.pm'}
33             ? 'Type::Registry'->for_class( $caller )->add_type( $type, $type_name )
34             : ( $Type::Registry::DELAYED{$caller}{$type_name} = $type )
35 1 100 33     14 unless( ref($caller) or $caller eq '-lexical' or $globals->{'lexical'} );
    50 33        
36 1         2 return map +( $_->{name} => $_->{code} ), @{ $type->exportables };
  1         2  
37             }
38              
39             sub new {
40 58     58 1 314 my $proto = shift;
41            
42 58 50       301 my %opts = ( @_ == 1 ) ? %{ $_[0] } : @_;
  0         0  
43             _croak
44             "Enum type constraints cannot have a parent constraint passed to the constructor"
45 58 100       220 if exists $opts{parent};
46             _croak
47             "Enum type constraints cannot have a constraint coderef passed to the constructor"
48 57 100       160 if exists $opts{constraint};
49             _croak
50             "Enum type constraints cannot have a inlining coderef passed to the constructor"
51 56 100       157 if exists $opts{inlined};
52 55 100       167 _croak "Need to supply list of values" unless exists $opts{values};
53            
54 26     26   205 no warnings 'uninitialized';
  26         60  
  26         86183  
55             $opts{values} = [
56             map "$_",
57 54 50       111 @{ ref $opts{values} eq 'ARRAY' ? $opts{values} : [ $opts{values} ] }
  54         419  
58             ];
59            
60 54         135 my %tmp;
61 54         95 undef $tmp{$_} for @{ $opts{values} };
  54         302  
62 54         366 $opts{unique_values} = [ sort keys %tmp ];
63            
64 54         439 my $xs_encoding = _xs_encoding( $opts{unique_values} );
65 54 50       206 if ( defined $xs_encoding ) {
66 54         241 my $xsub = Type::Tiny::XS::get_coderef_for( $xs_encoding );
67 54 50       2955 $opts{compiled_type_constraint} = $xsub if $xsub;
68             }
69            
70 54 100 100     243 if ( defined $opts{coercion} and !ref $opts{coercion} and 1 eq $opts{coercion} )
      66        
71             {
72 2         6 delete $opts{coercion};
73             $opts{_build_coercion} = sub {
74 2     2   12 require Types::Standard;
75 2         7 my $c = shift;
76 2         10 my $t = $c->type_constraint;
77             $c->add_type_coercions(
78             Types::Standard::Str(),
79 9 50       105 sub { $t->closest_match( @_ ? $_[0] : $_ ) }
80 2         13 );
81 2         17 };
82             } #/ if ( defined $opts{coercion...})
83            
84 54         529 return $proto->SUPER::new( %opts );
85             } #/ sub new
86              
87             sub _lockdown {
88 54     54   149 my ( $self, $callback ) = @_;
89 54         208 $callback->( $self->{values}, $self->{unique_values} );
90             }
91              
92             sub new_union {
93 2     2 1 4 my $proto = shift;
94 2 50       9 my %opts = ( @_ == 1 ) ? %{ $_[0] } : @_;
  0         0  
95 2         5 my @types = @{ delete $opts{type_constraints} };
  2         8  
96 2         11 my @values = map @$_, @types;
97 2         11 $proto->new( %opts, values => \@values );
98             }
99              
100             sub new_intersection {
101 1     1 1 2 my $proto = shift;
102 1 50       5 my %opts = ( @_ == 1 ) ? %{ $_[0] } : @_;
  0         0  
103 1         3 my @types = @{ delete $opts{type_constraints} };
  1         3  
104 1         2 my %values; ++$values{$_} for map @$_, @types;
  1         5  
105 1         9 my @values = sort grep $values{$_}==@types, keys %values;
106 1         7 $proto->new( %opts, values => \@values );
107             }
108              
109 32     32 1 784 sub values { $_[0]{values} }
110 413     413 1 1600 sub unique_values { $_[0]{unique_values} }
111 37   66 37 1 201 sub constraint { $_[0]{constraint} ||= $_[0]->_build_constraint }
112              
113 109     109   262 sub _is_null_constraint { 0 }
114              
115             sub _build_display_name {
116 18     18   38 my $self = shift;
117 18         35 sprintf( "Enum[%s]", join q[,], @{ $self->unique_values } );
  18         63  
118             }
119              
120             sub is_word_safe {
121 7     7 1 12 my $self = shift;
122 7         16 return not grep /\W/, @{ $self->unique_values };
  7         19  
123             }
124              
125             sub exportables {
126 7     7 1 31 my ( $self, $base_name ) = @_;
127 7 50       31 if ( not $self->is_anon ) {
128 7   33     47 $base_name ||= $self->name;
129             }
130            
131 7         78 my $exportables = $self->SUPER::exportables( $base_name );
132            
133 7 100       28 if ( $self->is_word_safe ) {
134 6         35 require Eval::TypeTiny;
135 6         26 require B;
136 6         11 for my $value ( @{ $self->unique_values } ) {
  6         14  
137 20         180 push @$exportables, {
138             name => uc( sprintf '%s_%s', $base_name, $value ),
139             tags => [ 'constants' ],
140             code => Eval::TypeTiny::eval_closure(
141             source => sprintf( 'sub () { %s }', B::perlstring($value) ),
142             environment => {},
143             ),
144             };
145             }
146             }
147            
148 7         47 return $exportables;
149             }
150              
151             {
152             my $new_xs;
153            
154             #
155             # Note the fallback code for older Type::Tiny::XS cannot be tested as
156             # part of the coverage tests because they use the latest Type::Tiny::XS.
157             #
158            
159             sub _xs_encoding {
160 394     394   710 my $unique_values = shift;
161            
162 394         466 return undef unless Type::Tiny::_USE_XS;
163            
164 394 50       955 return undef if @$unique_values > 50; # RT 121957
165            
166 394 50       746 $new_xs = eval { Type::Tiny::XS->VERSION( "0.020" ); 1 } ? 1 : 0
  25 100       432  
  25         130  
167             unless defined $new_xs;
168 394 50       713 if ( $new_xs ) {
169 394         1595 require B;
170 394         3828 return sprintf(
171             "Enum[%s]",
172             join( ",", map B::perlstring( $_ ), @$unique_values )
173             );
174             }
175             else { # uncoverable statement
176 0 0       0 return undef if grep /\W/, @$unique_values; # uncoverable statement
177 0         0 return sprintf( "Enum[%s]", join( ",", @$unique_values ) ); # uncoverable statement
178             } # uncoverable statement
179             } #/ sub _xs_encoding
180             }
181              
182             {
183             my %cached;
184            
185             sub _build_constraint {
186 7     7   27 my $self = shift;
187            
188 7         22 my $regexp = $self->_regexp;
189 7 100       35 return $cached{$regexp} if $cached{$regexp};
190 6 50   43   38 my $coderef = ( $cached{$regexp} = sub { defined and m{\A(?:$regexp)\z} } );
  34         550  
191 6         32 Scalar::Util::weaken( $cached{$regexp} );
192 5         28 return $coderef;
193             }
194             }
195              
196             {
197             my %cached;
198            
199             sub _build_compiled_check {
200 0     0   0 my $self = shift;
201 0         0 my $regexp = $self->_regexp;
202 0 0       0 return $cached{$regexp} if $cached{$regexp};
203 0         0 my $coderef = ( $cached{$regexp} = $self->SUPER::_build_compiled_check( @_ ) );
204 0         0 Scalar::Util::weaken( $cached{$regexp} );
205 0         0 return $coderef;
206             }
207             }
208              
209             sub _regexp {
210 64     64   103 my $self = shift;
211 64   66     223 $self->{_regexp} ||= 'Type::Tiny::Enum::_Trie'->handle( $self->unique_values );
212             }
213              
214             sub as_regexp {
215 3     3 1 1733 my $self = shift;
216            
217 3 100       12 my $flags = @_ ? $_[0] : '';
218 3 100 66     27 unless ( defined $flags and $flags =~ /^[i]*$/ ) {
219 1         6 _croak(
220             "Unknown regexp flags: '$flags'; only 'i' currently accepted; stopped" );
221             }
222            
223 2         5 my $regexp = $self->_regexp;
224 2 100       92 $flags ? qr/\A(?:$regexp)\z/i : qr/\A(?:$regexp)\z/;
225             } #/ sub as_regexp
226              
227             sub can_be_inlined {
228 293     293 1 2491 !!1;
229             }
230              
231             sub inline_check {
232 340     340 1 542 my $self = shift;
233            
234 340         403 my $xsub;
235 340 50       630 if ( my $xs_encoding = _xs_encoding( $self->unique_values ) ) {
236 340         958 $xsub = Type::Tiny::XS::get_subname_for( $xs_encoding );
237 340 100 66     6607 return "$xsub\($_[0]\)" if $xsub && !$Type::Tiny::AvoidCallbacks;
238             }
239            
240 55         153 my $regexp = $self->_regexp;
241 55 50       237 my $code =
242             $_[0] eq '$_'
243             ? "(defined and !ref and m{\\A(?:$regexp)\\z})"
244             : "(defined($_[0]) and !ref($_[0]) and $_[0] =~ m{\\A(?:$regexp)\\z})";
245            
246 55 50       2738 return "do { $Type::Tiny::SafePackage $code }"
247             if $Type::Tiny::AvoidCallbacks;
248 0         0 return $code;
249             } #/ sub inline_check
250              
251             sub _instantiate_moose_type {
252 1     1   2 my $self = shift;
253 1         3 my %opts = @_;
254 1         2 delete $opts{parent};
255 1         3 delete $opts{constraint};
256 1         1 delete $opts{inlined};
257 1         5 require Moose::Meta::TypeConstraint::Enum;
258 1         4 return "Moose::Meta::TypeConstraint::Enum"
259             ->new( %opts, values => $self->values );
260             } #/ sub _instantiate_moose_type
261              
262             sub has_parent {
263 114     114 1 774 !!1;
264             }
265              
266             sub parent {
267 185     185 1 3805 require Types::Standard;
268 185         511 Types::Standard::Str();
269             }
270              
271             sub validate_explain {
272 1     1 1 3 my $self = shift;
273 1         3 my ( $value, $varname ) = @_;
274 1 50       5 $varname = '$_' unless defined $varname;
275            
276 1 50       10 return undef if $self->check( $value );
277            
278 1         8 require Type::Utils;
279 1 50       7 !defined( $value )
    50          
280             ? [
281             sprintf(
282             '"%s" requires that the value is defined',
283             $self,
284             ),
285             ]
286             : @$self < 13 ? [
287             sprintf(
288             '"%s" requires that the value is equal to %s',
289             $self,
290             Type::Utils::english_list( \"or", map B::perlstring( $_ ), @$self ),
291             ),
292             ]
293             : [
294             sprintf(
295             '"%s" requires that the value is one of an enumerated list of strings',
296             $self,
297             ),
298             ];
299             } #/ sub validate_explain
300              
301             sub has_sorter {
302 2     2 1 8 !!1;
303             }
304              
305             sub _enum_order_hash {
306 2     2   5 my $self = shift;
307 2         4 my %hash;
308 2         14 my $i = 0;
309 2         7 for my $value ( @{ $self->values } ) {
  2         8  
310 7 100       18 next if exists $hash{$value};
311 6         14 $hash{$value} = $i++;
312             }
313 2         11 return %hash;
314             } #/ sub _enum_order_hash
315              
316             sub sorter {
317 2     2 1 4 my $self = shift;
318 2         15 my %hash = $self->_enum_order_hash;
319             return [
320 15     15   246 sub { $_[0] <=> $_[1] },
321 9 100   9   83 sub { exists( $hash{ $_[0] } ) ? $hash{ $_[0] } : 2_100_000_000 },
322 2         24 ];
323             }
324              
325             my $canon;
326              
327             sub closest_match {
328 9     9 1 52 require Types::Standard;
329            
330 9         21 my ( $self, $given ) = ( shift, @_ );
331            
332 9 50       23 return unless Types::Standard::is_Str $given;
333            
334 9 50       29 return $given if $self->check( $given );
335            
336 9 50 66     139 $canon ||= eval(
337             $] lt '5.016'
338             ? q< sub { ( my $var = lc($_[0]) ) =~ s/(^\s+)|(\s+$)//g; $var } >
339             : q< sub { CORE::fc($_[0]) =~ s/(^\s+)|(\s+$)//gr; } >
340             );
341            
342 9   66     26 $self->{_lookups} ||= do {
343 1         3 my %lookups;
344 1         2 for ( @{ $self->values } ) {
  1         5  
345 3         58 my $key = $canon->( $_ );
346 3 50       8 next if exists $lookups{$key};
347 3         8 $lookups{$key} = $_;
348             }
349 1         5 \%lookups;
350             };
351            
352 9         213 my $cgiven = $canon->( $given );
353             return $self->{_lookups}{$cgiven}
354 9 100       42 if $self->{_lookups}{$cgiven};
355            
356 7         12 my $best;
357 7         10 VALUE: for my $possible ( @{ $self->values } ) {
  7         18  
358 21         43 my $stem = substr( $possible, 0, length $cgiven );
359 21 100       753 if ( $cgiven eq $canon->( $stem ) ) {
360 3 100 66     19 if ( defined( $best ) and length( $best ) >= length( $possible ) ) {
361 1         3 next VALUE;
362             }
363 2         6 $best = $possible;
364             }
365             }
366            
367 7 100       28 return $best if defined $best;
368            
369 5 100       24 return $self->values->[$given]
370             if Types::Standard::is_Int $given;
371            
372 1         7 return $given;
373             } #/ sub closest_match
374              
375             push @Type::Tiny::CMP, sub {
376             my $A = shift->find_constraining_type;
377             my $B = shift->find_constraining_type;
378             return Type::Tiny::CMP_UNKNOWN
379             unless $A->isa( __PACKAGE__ ) && $B->isa( __PACKAGE__ );
380            
381             my %seen;
382             for my $word ( @{ $A->unique_values } ) {
383             $seen{$word} += 1;
384             }
385             for my $word ( @{ $B->unique_values } ) {
386             $seen{$word} += 2;
387             }
388            
389             my $values = join( '', CORE::values %seen );
390             if ( $values =~ /^3*$/ ) {
391             return Type::Tiny::CMP_EQUIVALENT;
392             }
393             elsif ( $values !~ /2/ ) {
394             return Type::Tiny::CMP_SUPERTYPE;
395             }
396             elsif ( $values !~ /1/ ) {
397             return Type::Tiny::CMP_SUBTYPE;
398             }
399            
400             return Type::Tiny::CMP_UNKNOWN;
401             };
402              
403             package # stolen from Regexp::Trie
404             Type::Tiny::Enum::_Trie;
405 10     10   29 sub new { bless {} => shift }
406              
407             sub add {
408 28     28   47 my $self = shift;
409 28         44 my $str = shift;
410 28         231 my $ref = $self;
411 28         230 for my $char ( split //, $str ) {
412 84   100     361 $ref->{$char} ||= {};
413 84         131 $ref = $ref->{$char};
414             }
415 28         72 $ref->{''} = 1; # { '' => 1 } as terminator
416 28         70 $self;
417             } #/ sub add
418              
419             sub _regexp {
420 82     82   117 my $self = shift;
421 82 100 100     316 return if $self->{''} and scalar keys %$self == 1; # terminator
422 56         79 my ( @alt, @cc );
423 56         75 my $q = 0;
424 56         161 for my $char ( sort keys %$self ) {
425 74         124 my $qchar = quotemeta $char;
426 74 100       179 if ( ref $self->{$char} ) {
427 72 100       232 if ( defined( my $recurse = _regexp( $self->{$char} ) ) ) {
428 46         118 push @alt, $qchar . $recurse;
429             }
430             else {
431 26         85 push @cc, $qchar;
432             }
433             }
434             else {
435 2         12 $q = 1;
436             }
437             } #/ for my $char ( sort keys...)
438 56         114 my $cconly = !@alt;
439 56 100       188 @cc and push @alt, @cc == 1 ? $cc[0] : '[' . join( '', @cc ) . ']';
    100          
440 56 100       129 my $result = @alt == 1 ? $alt[0] : '(?:' . join( '|', @alt ) . ')';
441 56 50       120 $q and $result = $cconly ? "$result?" : "(?:$result)?";
    100          
442 56         218 return $result;
443             } #/ sub _regexp
444              
445             sub handle {
446 10     10   22 my $class = shift;
447 10         25 my ( $vals ) = @_;
448 10 50       34 return '(?!)' unless @$vals;
449 10         35 my $self = $class->new;
450 10         37 $self->add( $_ ) for @$vals;
451 10         59 $self->_regexp;
452             }
453              
454             1;
455              
456             __END__
457              
458             =pod
459              
460             =encoding utf-8
461              
462             =head1 NAME
463              
464             Type::Tiny::Enum - string enum type constraints
465              
466             =head1 SYNOPSIS
467              
468             Using via L<Types::Standard>:
469              
470             package Horse {
471             use Moo;
472             use Types::Standard qw( Str Enum );
473            
474             has name => ( is => 'ro', isa => Str );
475             has status => ( is => 'ro', isa => Enum[ 'alive', 'dead' ] );
476            
477             sub neigh {
478             my ( $self ) = @_;
479             return if $self->status eq 'dead';
480             ...;
481             }
482             }
483              
484             Using Type::Tiny::Enum's export feature:
485              
486             package Horse {
487             use Moo;
488             use Types::Standard qw( Str );
489             use Type::Tiny::Enum Status => [ 'alive', 'dead' ];
490            
491             has name => ( is => 'ro', isa => Str );
492             has status => ( is => 'ro', isa => Status, default => STATUS_ALIVE );
493            
494             sub neigh {
495             my ( $self ) = @_;
496             return if $self->status eq STATUS_DEAD;
497             ...;
498             }
499             }
500              
501             Using Type::Tiny::Enum's object-oriented interface:
502              
503             package Horse {
504             use Moo;
505             use Types::Standard qw( Str );
506             use Type::Tiny::Enum;
507            
508             my $Status = Type::Tiny::Enum->new(
509             name => 'Status',
510             values => [ 'alive', 'dead' ],
511             );
512            
513             has name => ( is => 'ro', isa => Str );
514             has status => ( is => 'ro', isa => $Status, default => $Status->[0] );
515            
516             sub neigh {
517             my ( $self ) = @_;
518             return if $self->status eq $Status->[0];
519             ...;
520             }
521             }
522              
523             =head1 STATUS
524              
525             This module is covered by the
526             L<Type-Tiny stability policy|Type::Tiny::Manual::Policies/"STABILITY">.
527              
528             =head1 DESCRIPTION
529              
530             Enum type constraints.
531              
532             This package inherits from L<Type::Tiny>; see that for most documentation.
533             Major differences are listed below:
534              
535             =head2 Constructors
536              
537             The C<new> constructor from L<Type::Tiny> still works, of course. But there
538             is also:
539              
540             =over
541              
542             =item C<< new_union( type_constraints => \@enums, %opts ) >>
543              
544             Creates a new enum type constraint which is the union of existing enum
545             type constraints.
546              
547             =item C<< new_intersection( type_constraints => \@enums, %opts ) >>
548              
549             Creates a new enum type constraint which is the intersection of existing enum
550             type constraints.
551              
552             =back
553              
554             =head2 Attributes
555              
556             =over
557              
558             =item C<values>
559              
560             Arrayref of allowable value strings. Non-string values (e.g. objects with
561             overloading) will be stringified in the constructor.
562              
563             =item C<constraint>
564              
565             Unlike Type::Tiny, you I<cannot> pass a constraint coderef to the constructor.
566             Instead rely on the default.
567              
568             =item C<inlined>
569              
570             Unlike Type::Tiny, you I<cannot> pass an inlining coderef to the constructor.
571             Instead rely on the default.
572              
573             =item C<parent>
574              
575             Parent is always B<Types::Standard::Str>, and cannot be passed to the
576             constructor.
577              
578             =item C<unique_values>
579              
580             The list of C<values> but sorted and with duplicates removed. This cannot
581             be passed to the constructor.
582              
583             =item C<coercion>
584              
585             If C<< coercion => 1 >> is passed to the constructor, the type will have a
586             coercion using the C<closest_match> method.
587              
588             =back
589              
590             =head2 Methods
591              
592             =over
593              
594             =item C<as_regexp>
595              
596             Returns the enum as a regexp which strings can be checked against. If you're
597             checking I<< a lot >> of strings, then using this regexp might be faster than
598             checking each string against
599              
600             my $enum = Type::Tiny::Enum->new(...);
601             my $check = $enum->compiled_check;
602             my $re = $enum->as_regexp;
603            
604             # fast
605             my @valid_tokens = grep $enum->check($_), @all_tokens;
606            
607             # faster
608             my @valid_tokens = grep $check->($_), @all_tokens;
609            
610             # fastest
611             my @valid_tokens = grep /$re/, @all_tokens;
612              
613             You can get a case-insensitive regexp using C<< $enum->as_regexp('i') >>.
614              
615             =item C<closest_match>
616              
617             Returns the closest match in the enum for a string.
618              
619             my $enum = Type::Tiny::Enum->new(
620             values => [ qw( foo bar baz quux ) ],
621             );
622            
623             say $enum->closest_match("FO"); # ==> foo
624              
625             It will try to find an exact match first, fall back to a case-insensitive
626             match, if it still can't find one, will try to find a head substring match,
627             and finally, if given an integer, will use that as an index.
628              
629             my $enum = Type::Tiny::Enum->new(
630             values => [ qw( foo bar baz quux ) ],
631             );
632            
633             say $enum->closest_match( 0 ); # ==> foo
634             say $enum->closest_match( 1 ); # ==> bar
635             say $enum->closest_match( 2 ); # ==> baz
636             say $enum->closest_match( -1 ); # ==> quux
637              
638             =item C<< is_word_safe >>
639              
640             Returns true if none of the values in the enumeration contain a non-word
641             character. Word characters include letters, numbers, and underscores, but
642             not most punctuation or whitespace.
643              
644             =back
645              
646             =head2 Exports
647              
648             Type::Tiny::Enum can be used as an exporter.
649              
650             use Type::Tiny::Enum Status => [ 'dead', 'alive' ];
651              
652             This will export the following functions into your namespace:
653              
654             =over
655              
656             =item C<< Status >>
657              
658             =item C<< is_Status( $value ) >>
659              
660             =item C<< assert_Status( $value ) >>
661              
662             =item C<< to_Status( $value ) >>
663              
664             =item C<< STATUS_DEAD >>
665              
666             =item C<< STATUS_ALIVE >>
667              
668             =back
669              
670             Multiple enumerations can be exported at once:
671              
672             use Type::Tiny::Enum (
673             Status => [ 'dead', 'alive' ],
674             TaxStatus => [ 'paid', 'pending' ],
675             );
676              
677             =head2 Overloading
678              
679             =over
680              
681             =item *
682              
683             Arrayrefification calls C<values>.
684              
685             =back
686              
687             =head1 BUGS
688              
689             Please report any bugs to
690             L<https://github.com/tobyink/p5-type-tiny/issues>.
691              
692             =head1 SEE ALSO
693              
694             L<Type::Tiny::Manual>.
695              
696             L<Type::Tiny>.
697              
698             L<Moose::Meta::TypeConstraint::Enum>.
699              
700             =head1 AUTHOR
701              
702             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
703              
704             =head1 COPYRIGHT AND LICENCE
705              
706             This software is copyright (c) 2013-2014, 2017-2023 by Toby Inkster.
707              
708             This is free software; you can redistribute it and/or modify it under
709             the same terms as the Perl 5 programming language system itself.
710              
711             =head1 DISCLAIMER OF WARRANTIES
712              
713             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
714             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
715             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.