File Coverage

blib/lib/Type/Tiny/Enum.pm
Criterion Covered Total %
statement 226 239 95.4
branch 93 118 78.8
condition 28 41 68.2
subroutine 46 47 97.8
pod 18 18 100.0
total 411 463 89.2


line stmt bran cond sub pod time code
1             package Type::Tiny::Enum;
2              
3 90     90   102556 use 5.008001;
  90         426  
4 90     90   570 use strict;
  90         190  
  90         2835  
5 90     90   666 use warnings;
  90         218  
  90         9073  
6              
7             BEGIN {
8 90     90   383 $Type::Tiny::Enum::AUTHORITY = 'cpan:TOBYINK';
9 90         10060 $Type::Tiny::Enum::VERSION = '2.010001';
10             }
11              
12             $Type::Tiny::Enum::VERSION =~ tr/_//d;
13              
14 5     5   43 sub _croak ($;@) { require Error::TypeTiny; goto \&Error::TypeTiny::croak }
  5         32  
15              
16 90     90   2050 use Exporter::Tiny 1.004001 ();
  90         13240  
  90         3998  
17 90     90   3354 use Type::Tiny ();
  90         207  
  90         35960  
18             our @ISA = qw( Type::Tiny Exporter::Tiny );
19              
20             __PACKAGE__->_install_overloads(
21 10     10   52 q[@{}] => sub { shift->values },
22             );
23              
24             sub _exporter_fail {
25 2     2   486 my ( $class, $type_name, $values, $globals ) = @_;
26 2         5 my $caller = $globals->{into};
27 2         8 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 2 50 33     62 unless( ref($caller) or $caller eq '-lexical' or $globals->{'lexical'} );
    100 66        
36 2         5 return map +( $_->{name} => $_->{code} ), @{ $type->exportables };
  2         10  
37             }
38              
39             sub new {
40 223     223 1 618730 my $proto = shift;
41            
42 223 50       1969 my %opts = ( @_ == 1 ) ? %{ $_[0] } : @_;
  0         0  
43             _croak
44             "Enum type constraints cannot have a parent constraint passed to the constructor"
45 223 100       1220 if exists $opts{parent};
46             _croak
47             "Enum type constraints cannot have a constraint coderef passed to the constructor"
48 222 100       989 if exists $opts{constraint};
49             _croak
50             "Enum type constraints cannot have a sorter coderef passed to the constructor"
51 221 50       816 if exists $opts{sorter};
52             _croak
53             "Enum type constraints cannot have a inlining coderef passed to the constructor"
54 221 100       898 if exists $opts{inlined};
55 220 100       1247 _croak "Need to supply list of values" unless exists $opts{values};
56            
57 90     90   1016 no warnings 'uninitialized';
  90         239  
  90         198895  
58             $opts{values} = [
59             map "$_",
60 219 50       467 @{ ref $opts{values} eq 'ARRAY' ? $opts{values} : [ $opts{values} ] }
  219         4566  
61             ];
62            
63 219         2723 my %tmp;
64 219         440 undef $tmp{$_} for @{ $opts{values} };
  219         3766  
65 219         3632 $opts{unique_values} = [ sort keys %tmp ];
66            
67 219         1151 my $xs_encoding = _xs_encoding( $opts{unique_values} );
68 219 100       1000 if ( defined $xs_encoding ) {
69 213         1363 my $xsub = Type::Tiny::XS::get_coderef_for( $xs_encoding );
70 213 50       23777 $opts{compiled_type_constraint} = $xsub if $xsub;
71             }
72            
73 219 100 100     1257 if ( defined $opts{coercion} and !ref $opts{coercion} and 1 eq $opts{coercion} )
      66        
74             {
75 3         7 delete $opts{coercion};
76             $opts{_build_coercion} = sub {
77 3     3   15 require Types::Standard;
78 3         6 my $c = shift;
79 3         12 my $t = $c->type_constraint;
80             $c->add_type_coercions(
81             Types::Standard::Str(),
82 9 50       97 sub { $t->closest_match( @_ ? $_[0] : $_ ) }
83 3         18 );
84 3         22 };
85             } #/ if ( defined $opts{coercion...})
86            
87 219         6684 return $proto->SUPER::new( %opts );
88             } #/ sub new
89              
90             sub _lockdown {
91 219     219   712 my ( $self, $callback ) = @_;
92 219         1240 $callback->( $self->{values}, $self->{unique_values} );
93             }
94              
95             sub new_union {
96 1     1 1 4 my $proto = shift;
97 1 50       4 my %opts = ( @_ == 1 ) ? %{ $_[0] } : @_;
  0         0  
98 1         3 my @types = @{ delete $opts{type_constraints} };
  1         4  
99 1         11 my @values = map @$_, @types;
100 1         10 $proto->new( %opts, values => \@values );
101             }
102              
103             sub new_intersection {
104 1     1 1 3 my $proto = shift;
105 1 50       5 my %opts = ( @_ == 1 ) ? %{ $_[0] } : @_;
  0         0  
106 1         2 my @types = @{ delete $opts{type_constraints} };
  1         3  
107 1         3 my %values; ++$values{$_} for map @$_, @types;
  1         6  
108 1         7 my @values = sort grep $values{$_}==@types, keys %values;
109 1         9 $proto->new( %opts, values => \@values );
110             }
111              
112 55     55 1 1147 sub values { $_[0]{values} }
113 600     600 1 3368 sub unique_values { $_[0]{unique_values} }
114 59   66 59 1 486 sub constraint { $_[0]{constraint} ||= $_[0]->_build_constraint }
115 119 100   119 1 616 sub use_eq { return $_[0]{use_eq} if exists $_[0]{use_eq}; $_[0]{use_eq} = $_[0]->_build_use_eq }
  14         59  
116              
117 123     123   447 sub _is_null_constraint { 0 }
118              
119             sub _build_display_name {
120 21     21   45 my $self = shift;
121 21         46 sprintf( "Enum[%s]", join q[,], @{ $self->unique_values } );
  21         63  
122             }
123              
124             sub _build_use_eq {
125 14     14   42 my $self = shift;
126 14         69 !Type::Tiny::_USE_XS and @{ $self->unique_values } <= 5;
127             }
128              
129             sub is_word_safe {
130 12     8 1 32 my $self = shift;
131 8         18 return not grep /\W/, @{ $self->unique_values };
  8         28  
132             }
133              
134             sub exportables {
135 8     8 1 103 my ( $self, $base_name ) = @_;
136 8 50       66 if ( not $self->is_anon ) {
137 8   33     43 $base_name ||= $self->name;
138             }
139            
140 8         80 my $exportables = $self->SUPER::exportables( $base_name );
141            
142 8 100       41 if ( $self->is_word_safe ) {
143 7         50 require Eval::TypeTiny;
144 7         29 require B;
145 7         14 for my $value ( @{ $self->unique_values } ) {
  7         21  
146 22         260 push @$exportables, {
147             name => uc( sprintf '%s_%s', $base_name, $value ),
148             tags => [ 'constants' ],
149             code => Eval::TypeTiny::eval_closure(
150             source => sprintf( 'sub () { %s }', B::perlstring($value) ),
151             environment => {},
152             ),
153             };
154             }
155             }
156            
157 8         84 return $exportables;
158             }
159              
160             {
161             my $new_xs;
162            
163             #
164             # Note the fallback code for older Type::Tiny::XS cannot be tested as
165             # part of the coverage tests because they use the latest Type::Tiny::XS.
166             #
167            
168             sub _xs_encoding {
169 731     731   2011 my $unique_values = shift;
170            
171 731         1210 return undef unless Type::Tiny::_USE_XS;
172            
173 667 50       2247 return undef if @$unique_values > 50; # RT 121957
174            
175 667 50       3707 $new_xs = eval { Type::Tiny::XS->VERSION( "0.020" ); 1 } ? 1 : 0
  87 100       1825  
  87         2070  
176             unless defined $new_xs;
177 667 50       2078 if ( $new_xs ) {
178 667         4319 require B;
179 667         15754 return sprintf(
180             "Enum[%s]",
181             join( ",", map B::perlstring( $_ ), @$unique_values )
182             );
183             }
184             else { # uncoverable statement
185 0 0       0 return undef if grep /\W/, @$unique_values; # uncoverable statement
186 0         0 return sprintf( "Enum[%s]", join( ",", @$unique_values ) ); # uncoverable statement
187             } # uncoverable statement
188             } #/ sub _xs_encoding
189             }
190              
191             {
192             my %cached;
193            
194             sub _build_constraint {
195 9     9   23 my $self = shift;
196            
197 9         31 my $regexp = $self->_regexp;
198 9 100       56 return $cached{$regexp} if $cached{$regexp};
199 6 50   45   64 my $coderef = ( $cached{$regexp} = sub { defined and m{\A(?:$regexp)\z} } );
  45         1127  
200 6         39 Scalar::Util::weaken( $cached{$regexp} );
201 6         50 return $coderef;
202             }
203             }
204              
205             {
206             my %cached;
207            
208             sub _build_compiled_check {
209 6     6   12 my $self = shift;
210 6         17 my $regexp = $self->_regexp;
211 6 100       37 return $cached{$regexp} if $cached{$regexp};
212 4         37 my $coderef = ( $cached{$regexp} = $self->SUPER::_build_compiled_check( @_ ) );
213 4         14 Scalar::Util::weaken( $cached{$regexp} );
214 4         29 return $coderef;
215             }
216             }
217              
218             sub _regexp {
219 105     105   232 my $self = shift;
220 105   66     447 $self->{_regexp} ||= 'Type::Tiny::Enum::_Trie'->handle( $self->unique_values );
221             }
222              
223             sub as_regexp {
224 3     3 1 2538 my $self = shift;
225            
226 3 100       14 my $flags = @_ ? $_[0] : '';
227 3 100 66     43 unless ( defined $flags and $flags =~ /^[i]*$/ ) {
228 1         11 _croak(
229             "Unknown regexp flags: '$flags'; only 'i' currently accepted; stopped" );
230             }
231            
232 2         9 my $regexp = $self->_regexp;
233 2 100       196 $flags ? qr/\A(?:$regexp)\z/i : qr/\A(?:$regexp)\z/;
234             } #/ sub as_regexp
235              
236             sub can_be_inlined {
237 451     451 1 2794 !!1;
238             }
239              
240             sub inline_check {
241 512     512 1 979 my $self = shift;
242            
243 512 100       1670 if ( my $xs_encoding = _xs_encoding( $self->unique_values ) ) {
244 454         2087 my $xsub = Type::Tiny::XS::get_subname_for( $xs_encoding );
245 454 100 66     11111 return "$xsub\($_[0]\)" if $xsub && !$Type::Tiny::AvoidCallbacks;
246             }
247            
248 115         220 my $code;
249 115 100       354 if ( $self->use_eq ) {
250 90     90   1069 use B ();
  90         249  
  90         185652  
251 27         29 my %seen;
252 27         22 my @vals = grep { not $seen{$_}++ } @{ $self->values };
  110         242  
  27         53  
253 27 50       62 if ( @vals == 1 ) {
254 0         0 $code = sprintf( '(defined %s and !ref %s and %s eq %s)', $_[0], $_[0], $_[0], B::perlstring($vals[0]) );
255             }
256             else {
257 27         42 $code = sprintf( '(defined %s and !ref %s and (%s))', $_[0], $_[0], join q{ or } => map { sprintf '(%s eq %s)', $_[0], B::perlstring($_) } @vals );
  110         357  
258             }
259             }
260             else {
261 88         1664 my $regexp = $self->_regexp;
262 88 100       471 $code =
263             $_[0] eq '$_'
264             ? "(defined and !ref and m{\\A(?:$regexp)\\z})"
265             : "(defined($_[0]) and !ref($_[0]) and $_[0] =~ m{\\A(?:$regexp)\\z})";
266             }
267            
268 115 100       4661 return "do { $Type::Tiny::SafePackage $code }"
269             if $Type::Tiny::AvoidCallbacks;
270 34         2179 return $code;
271             } #/ sub inline_check
272              
273             sub _instantiate_moose_type {
274 0     0   0 my $self = shift;
275 0         0 my %opts = @_;
276 0         0 delete $opts{parent};
277 0         0 delete $opts{constraint};
278 0         0 delete $opts{inlined};
279 0         0 require Moose::Meta::TypeConstraint::Enum;
280 0         0 return "Moose::Meta::TypeConstraint::Enum"
281             ->new( %opts, values => $self->values );
282             } #/ sub _instantiate_moose_type
283              
284             sub has_parent {
285 140     140 1 530 !!1;
286             }
287              
288             sub parent {
289 242     242 1 6338 require Types::Standard;
290 242         874 Types::Standard::Str();
291             }
292              
293             sub validate_explain {
294 1     1 1 3 my $self = shift;
295 1         3 my ( $value, $varname ) = @_;
296 1 50       5 $varname = '$_' unless defined $varname;
297            
298 1 50       11 return undef if $self->check( $value );
299            
300 1         9 require Type::Utils;
301 1 50       8 !defined( $value )
    50          
302             ? [
303             sprintf(
304             '"%s" requires that the value is defined',
305             $self,
306             ),
307             ]
308             : @$self < 13 ? [
309             sprintf(
310             '"%s" requires that the value is equal to %s',
311             $self,
312             Type::Utils::english_list( \"or", map B::perlstring( $_ ), @$self ),
313             ),
314             ]
315             : [
316             sprintf(
317             '"%s" requires that the value is one of an enumerated list of strings',
318             $self,
319             ),
320             ];
321             } #/ sub validate_explain
322              
323             sub has_sorter {
324 2     2 1 8 !!1;
325             }
326              
327             sub _enum_order_hash {
328 2     2   3 my $self = shift;
329 2         11 my %hash;
330 2         25 my $i = 0;
331 2         5 for my $value ( @{ $self->values } ) {
  2         6  
332 7 100       14 next if exists $hash{$value};
333 6         13 $hash{$value} = $i++;
334             }
335 2         11 return %hash;
336             } #/ sub _enum_order_hash
337              
338             sub sorter {
339 2     2 1 5 my $self = shift;
340 2         9 my %hash = $self->_enum_order_hash;
341             return [
342 15     15   231 sub { $_[0] <=> $_[1] },
343 9 100   9   59 sub { exists( $hash{ $_[0] } ) ? $hash{ $_[0] } : 2_100_000_000 },
344 2         14 ];
345             }
346              
347             my $canon;
348              
349             sub closest_match {
350 9     9 1 42 require Types::Standard;
351            
352 9         20 my ( $self, $given ) = ( shift, @_ );
353            
354 9 50       21 return unless Types::Standard::is_Str $given;
355            
356 9 50       24 return $given if $self->check( $given );
357            
358 9 50 66     261 $canon ||= eval(
359             $] lt '5.016'
360             ? q< sub { ( my $var = lc($_[0]) ) =~ s/(^\s+)|(\s+$)//g; $var } >
361             : q< sub { CORE::fc($_[0]) =~ s/(^\s+)|(\s+$)//gr; } >
362             );
363            
364 9   66     20 $self->{_lookups} ||= do {
365 1         1 my %lookups;
366 1         3 for ( @{ $self->values } ) {
  1         3  
367 3         43 my $key = $canon->( $_ );
368 3 50       7 next if exists $lookups{$key};
369 3         8 $lookups{$key} = $_;
370             }
371 1         20 \%lookups;
372             };
373            
374 9         182 my $cgiven = $canon->( $given );
375             return $self->{_lookups}{$cgiven}
376 9 100       36 if $self->{_lookups}{$cgiven};
377            
378 7         9 my $best;
379 7         10 VALUE: for my $possible ( @{ $self->values } ) {
  7         15  
380 21         32 my $stem = substr( $possible, 0, length $cgiven );
381 21 100       236 if ( $cgiven eq $canon->( $stem ) ) {
382 3 100 66     11 if ( defined( $best ) and length( $best ) >= length( $possible ) ) {
383 1         3 next VALUE;
384             }
385 2         4 $best = $possible;
386             }
387             }
388            
389 7 100       23 return $best if defined $best;
390            
391 5 100       16 return $self->values->[$given]
392             if Types::Standard::is_Int $given;
393            
394 1         5 return $given;
395             } #/ sub closest_match
396              
397             push @Type::Tiny::CMP, sub {
398             my $A = shift->find_constraining_type;
399             my $B = shift->find_constraining_type;
400             return Type::Tiny::CMP_UNKNOWN
401             unless $A->isa( __PACKAGE__ ) && $B->isa( __PACKAGE__ );
402            
403             my %seen;
404             for my $word ( @{ $A->unique_values } ) {
405             $seen{$word} += 1;
406             }
407             for my $word ( @{ $B->unique_values } ) {
408             $seen{$word} += 2;
409             }
410            
411             my $values = join( '', CORE::values %seen );
412             if ( $values =~ /^3*$/ ) {
413             return Type::Tiny::CMP_EQUIVALENT;
414             }
415             elsif ( $values !~ /2/ ) {
416             return Type::Tiny::CMP_SUPERTYPE;
417             }
418             elsif ( $values !~ /1/ ) {
419             return Type::Tiny::CMP_SUBTYPE;
420             }
421            
422             return Type::Tiny::CMP_UNKNOWN;
423             };
424              
425             package # stolen from Regexp::Trie
426             Type::Tiny::Enum::_Trie;
427 16     16   72 sub new { bless {} => shift }
428              
429             sub add {
430 144     144   218 my $self = shift;
431 144         231 my $str = shift;
432 144         203 my $ref = $self;
433 144         471 for my $char ( split //, $str ) {
434 1041   100     4446 $ref->{$char} ||= {};
435 1041         1904 $ref = $ref->{$char};
436             }
437 144         378 $ref->{''} = 1; # { '' => 1 } as terminator
438 144         422 $self;
439             } #/ sub add
440              
441             sub _regexp {
442 897     897   1307 my $self = shift;
443 897 100 100     2386 return if $self->{''} and scalar keys %$self == 1; # terminator
444 761         1150 my ( @alt, @cc );
445 761         1307 my $q = 0;
446 761         2045 for my $char ( sort keys %$self ) {
447 889         1491 my $qchar = quotemeta $char;
448 889 100       1648 if ( ref $self->{$char} ) {
449 881 100       1880 if ( defined( my $recurse = _regexp( $self->{$char} ) ) ) {
450 745         1803 push @alt, $qchar . $recurse;
451             }
452             else {
453 136         445 push @cc, $qchar;
454             }
455             }
456             else {
457 8         20 $q = 1;
458             }
459             } #/ for my $char ( sort keys...)
460 761         1270 my $cconly = !@alt;
461 761 100       1581 @cc and push @alt, @cc == 1 ? $cc[0] : '[' . join( '', @cc ) . ']';
    100          
462 761 100       1493 my $result = @alt == 1 ? $alt[0] : '(?:' . join( '|', @alt ) . ')';
463 761 100       1523 $q and $result = $cconly ? "$result?" : "(?:$result)?";
    100          
464 761         2348 return $result;
465             } #/ sub _regexp
466              
467             sub handle {
468 16     16   42 my $class = shift;
469 16         69 my ( $vals ) = @_;
470 16 50       83 return '(?!)' unless @$vals;
471 16         65 my $self = $class->new;
472 16         91 $self->add( $_ ) for @$vals;
473 16         168 $self->_regexp;
474             }
475              
476             1;
477              
478             __END__