File Coverage

blib/lib/Coerce/Types/Standard.pm
Criterion Covered Total %
statement 185 228 81.1
branch 32 54 59.2
condition 10 28 35.7
subroutine 58 62 93.5
pod 2 32 6.2
total 287 404 71.0


line stmt bran cond sub pod time code
1             package Coerce::Types::Standard;
2              
3 9     9   562840 use 5.006;
  9         70  
4 9     9   44 use strict;
  9         13  
  9         184  
5 9     9   38 use warnings;
  9         19  
  9         535  
6 9     9   40 use Scalar::Util qw/blessed reftype refaddr/;
  9         16  
  9         473  
7              
8 9     9   1806 use parent 'Types::Standard';
  9         1819  
  9         47  
9            
10             our @EXPORT_OK = ( Types::Standard->type_names );
11              
12             our $meta = __PACKAGE__->meta;
13             our $VERSION = '0.000005';
14              
15             our (%entity, %recurse, %compare, $esc, $unesc, $path);
16             BEGIN {
17 9     9   521993 %entity = (
18             encode => {
19             q{&} => q{&}, q{"} => q{"}, q{'} => q{'}, q{<} => q{<}, q{>} => q{>}
20             }
21             );
22 9         25 my @keys = keys %{$entity{encode}};
  9         44  
23             $entity{decode} = +{ map {
24 9         25 $entity{encode}->{$_} => $_
  45         121  
25             } @keys };
26 9         28 $entity{encode}->{regex} = join "|", map { quotemeta($_) } @keys;
  45         96  
27 9         21 $entity{decode}->{regex} = join "|", map { quotemeta($_) } keys %{$entity{decode}};
  45         141  
  9         29  
28             $entity{escape} = +{
29             map {
30 9         32 chr($_) => sprintf("%%%02X", $_)
  2304         6406  
31             } (0..255)
32             };
33             $entity{unescape} = +{
34             map {
35 2304         4837 $entity{escape}->{$_} => $_
36 9         174 } keys %{$entity{escape}}
  9         194  
37             };
38 9         302 $esc = qr/[^A-Za-z0-9\-\._~]/;
39 9         29 $unesc = qr/[0-9A-Fa-f]{2}/;
40 9         25 $path = qr|^(([a-z][a-z0-9+\-.]*):(!?\/\/([^\/?#]+))?)?([a-z0-9\-._~%!\$\&'()*+,;=:@\/]*)?(\?[a-z0-9\-._~%!\$\&'()*+,;=:@\/]*)?(#[a-z0-9\-._~%!\$\&'()*+,;=:@\/]*)|;
41             %recurse = (
42 0         0 ARRAY => sub { return map { recurse($_, $_[1], $_[2]) } @{ $_[0] } },
  0         0  
  0         0  
43 0         0 HASH => sub { do { $_[0]->{$_} = recurse($_[0]->{$_}, $_[1], $_[2]) } for keys %{ $_[0] }; $_[0] },
  0         0  
  0         0  
  0         0  
44 0 0       0 SCALAR => sub { ${$_[0]} =~ m/^[0-9.]+$/g ? $_[0] : do { ${$_[0]} =~ s/^(.*)$/recurse(${$_[1]})/e; $_[0]; }; },
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
45 9         83 );
46             %compare = (
47             ARRAY => sub {
48 2         5 my $recurse = shift;
49 2         3 my @length = sort { $a < $b } map { scalar @{ $_ } } (@_);
  2         6  
  4         5  
  4         12  
50 2 100       7 for my $i (0 .. $length[0] - 1) { compare($recurse, map { $_->[$i] } @_) or return 0; }
  3         5  
  6         13  
51 1         10 1;
52             },
53             HASH => sub {
54 0         0 my $recurse = shift;
55 0         0 for my $k (combine_keys(@_)) {
56 0 0       0 compare($recurse, map { $_->{$k} } @_) or return 0;
  0         0  
57             }
58 0         0 1;
59             },
60 0         0 SCALAR => sub { compare(shift, map {${$_}} @_) },
  0         0  
  0         0  
61 3         4 MAGIC => sub { my %t; shift; map { $t{$_}++ } @_; scalar keys %t == 1; },
  3         4  
  3         6  
  6         12  
  3         25  
62 9         355 );
63             }
64             {
65             # all powerfull
66 9     9   77 no strict 'refs';
  9         19  
  9         23752  
67             my $counter = 0;
68             *{"Type::Tiny::by"} = sub {
69 29     29   789 my ($pn, $parent, $hide, $act) = ($_[0]->name, shift, shift);
70 29 100   13   497 $act = ref $hide ? sub { compare(\%compare, @_) } : sub { $_[0] =~ m/$_[1]/; };
  4         94  
  82         1603  
71             my $self = do {
72             $_ =~ m/^$pn/ && exists $meta->{types}->{$_}->{abuse}
73             && $act->($meta->{types}->{$_}->{abuse}, $hide)
74             and return $meta->{types}->{$_} foreach $meta->type_names;
75             undef;
76             } || $meta->add_type({
77             name => $parent->name . $counter++,
78             parent => $parent->{abuse_parent} && $parent->{abuse_parent}->($hide) || $parent,
79             coercion => $parent->{abuse}->($hide),
80             abuse => $hide,
81 29   33     51 ($parent->{abuse_constraint} ? (constraint => $parent->{abuse_constraint}->($hide)) : ())
82             });
83 27         19060 return $self;
84             };
85             }
86              
87             $meta->add_type({
88             name => 'StrToArray',
89             parent => scalar $meta->ArrayRef,
90             abuse => \&_strToArray
91             });
92              
93             sub _strToArray {
94 3     3   32 my $hide = shift;
95             return sub {
96 5 50   5   22298 defined $_[0] ? [split $hide, $_[0]] : $_[0];
97             }
98 3         26 }
99              
100             $meta->add_type({
101             name => 'StrToHash',
102             parent => scalar $meta->HashRef,
103             abuse => \&_strToHash
104             });
105              
106             sub _strToHash {
107 3     3   32 my $hide = shift;
108             return sub {
109 3 50   3   23265 defined $_[0] ? +{split $hide, $_[0]} : $_[0];
110             }
111 3         27 }
112              
113             $meta->add_type({
114             name => 'StrSR',
115             parent => scalar $meta->Str,
116             abuse_constraint => \&search_replace_constraint,
117             abuse => \&search_replace
118             });
119              
120             sub search_replace_constraint {
121 2     2 0 6 my ($sr) = (quotemeta($_[0][0]));
122             return sub {
123 6     6   19224 my $str = shift;
124 6         41 ! ($str =~ m/$sr/);
125 2         21 };
126             }
127              
128             sub search_replace {
129 2     2 0 38 my (@sr) = (quotemeta($_[0][0]), $_[0][1]);
130             return sub {
131 3     3   19 my $str = shift;
132 3 50       39 ref $sr[1] eq 'CODE'
133 0         0 ? $str =~ s/($sr[0])/$sr[1]->($1)/ego
134             : $str =~ s/($sr[0])/$sr[1]/g;
135 3         54 return $str;
136 2         17 };
137             }
138              
139              
140              
141             $meta->add_type({
142             name => 'ArrayToHash',
143             parent => scalar $meta->HashRef,
144             coercion => sub {
145             +{
146             @{ $_[0] }
147             };
148             },
149             abuse => \&_hash
150             });
151              
152             sub _hash {
153 5     5   59 my $hide = sprintf "array_to_hash_%s", shift;
154 5         35 return \&$hide;
155             }
156              
157             # issues with the following is that arrays are not always flat *|o|*
158             sub array_to_hash_reverse {
159 1     1 0 548 my @array = @{$_[0]};
  1         3  
160 1         2 my %hash;
161 1         3 while (@array) {
162 2         4 my ($even, $odd) = (shift @array, shift @array);
163 2         7 $hash{$odd} = $even
164             }
165 1         17 return \%hash;
166             }
167              
168             sub array_to_hash_odd {
169 1     1 0 539 my @array = @{$_[0]};
  1         2  
170 1         4 return +{ (map {$array[$_]} grep {$_ & 1} 1 .. scalar @array - 1) };
  2         20  
  3         6  
171             }
172              
173             sub array_to_hash_even {
174 1     1 0 701 my @array = @{$_[0]};
  1         3  
175 1         4 return +{ (map {$array[$_]} grep {not $_ & 1} 0 .. scalar @array - 1) };
  2         21  
  4         8  
176             }
177              
178             sub array_to_hash_flat {
179 1     1 0 552 return +{ _flat($_[0]) };
180             }
181              
182             sub array_to_hash_merge {
183             return +{
184 1     1 0 580 map { %{$_} } grep { ref $_ eq 'HASH' } @{$_}
  2         3  
  2         22  
  2         7  
  1         2  
185             }
186             }
187              
188             $meta->add_type({
189             name => 'HashToArray',
190             parent => scalar $meta->ArrayRef,
191             coercion => sub {
192             defined $_[0] ? [
193             map { $_, $_[0]->{$_} } sort keys %{ $_[0] }
194             ] : $_[0];
195             },
196             abuse => \&_arrays
197             });
198              
199             sub _arrays {
200 3     3   37 my $hide = sprintf ('hash_to_array_%s', shift);
201 3         27 \&$hide;
202             }
203              
204             sub hash_to_array_keys {
205 1     1 0 554 return [ sort keys %{ $_[0] } ];
  1         21  
206             }
207              
208             sub hash_to_array_values {
209 1     1 0 537 return [ sort values %{ $_[0] } ];
  1         20  
210             }
211              
212             sub hash_to_array_flat {
213 1     1 0 679 return [_flat($_[0])];
214             }
215              
216             sub _flat {
217 2     2   6 my @lazy;
218             my %r = (
219 4     4   6 ARRAY => sub { map { recurse($_[0], $_) } @{ $_[1] } },
  6         12  
  4         9  
220 2 50   2   5 HASH => sub { do { recurse($_[0], $_) && recurse($_[0], $_[1]->{$_}); } for sort keys %{ $_[1] }; },
  2         10  
  3         15  
221 0     0   0 SCALAR => sub { recurse($_[0], ${$_[1]}) },
  0         0  
222 8     8   13 MAGIC => sub { push @lazy, $_[1] },
223 2         22 );
224 2         11 recurse(\%r, $_[0]);
225 2         62 return @lazy;
226             }
227              
228             $meta->add_type({
229             name => 'HTML',
230             parent => scalar $meta->Str,
231             abuse_constraint => \&_html_constraint,
232             abuse => \&_html
233             });
234              
235             sub _html_constraint {
236 2     2   6 my $hide = sprintf('constraint_%s', shift);
237 2         17 \&$hide;
238             }
239              
240             sub _html {
241 2     2   33 my $hide = sprintf('%s', shift);
242 2         14 \&$hide;
243             }
244              
245             sub constraint_encode_entity {
246 2     2 0 479 my ($str, %encode) = (shift, %{ $entity{encode} });
  2         13  
247 2 100       45 $str =~ m/($encode{regex})(?![a-z#]+;)/ ? 0 : 1;
248             }
249              
250             sub encode_entity {
251 1     1 1 8 my ($str, %encode) = (shift, %{ $entity{encode} });
  1         5  
252 1         21 $str =~ s/($encode{regex})/$encode{$1}/eg;
  7         20  
253 1         20 return $str;
254             }
255              
256             sub constraint_decode_entity {
257 2 100   2 0 19424 shift =~ m/&([a-z#]+;)/ ? 0 : 1;
258             }
259              
260             sub decode_entity {
261 1     1 1 8 my ($str, %decode) = (shift, %{ $entity{decode} });
  1         9  
262 1         36 $str =~ s/($decode{regex})/$decode{$1}/eg;
  7         20  
263 1         21 return $str;
264             }
265              
266             $meta->add_type({
267             name => 'URI',
268             parent => scalar $meta->Object,
269             constraint => sub {
270             my $obj = ref $_[0];
271             $obj =~ m!^URI! ? 1 : 0;
272             },
273             coercion => sub {
274             require URI;
275             my @args = ref $_[0] ? @{ $_[0] } : $_[0];
276             my $queryForm = pop @args if ref $args[scalar @args - 1] eq 'HASH';
277             my $uri = URI->new(@args);
278             $uri->query_form($queryForm) if $queryForm;
279             return $uri;
280             },
281             abuse_parent => \&_uri_change,
282             abuse_constraint => \&_uri_constraint,
283             abuse => \&_uri
284             });
285              
286             sub _uri_change {
287 9     9   51 my $hide = shift;
288 9 100       49 return scalar $meta->Str if $hide =~ m/^escape|unescape|schema|host|path|query_string|fragment$/;
289 2         9 return scalar $meta->HashRef;
290             }
291              
292             sub _uri_constraint {
293 9     9   17 my $hide = sprintf "constraint_uri_%s", shift;
294 9         54 \&$hide;
295             }
296              
297             # I don't know why, just don't ask
298             sub constraint_uri_schema {
299 2     2 0 509 $_[0] =~ m/$path/;
300 2 100 33     20 $4 || $5 || $6 || $7 ? 0 : 1;
301             }
302              
303             sub constraint_uri_host {
304 2     2 0 523 $_[0] =~ m/$path/;
305 2 100 33     16 $2 || $5 || $6 || $7 ? 0 : 1;
306             }
307              
308             sub constraint_uri_path {
309 2     2 0 557 $_[0] =~ m/$path/;
310 2 100 33     18 $2 || $4 || $6 || $7 ? 0 : 1;
311             }
312              
313             sub constraint_uri_query_string {
314 2     2 0 509 $_[0] =~ m/$path/;
315 2 100 33     19 $2 || $4 || $5 || $7 ? 0 : 1;
316             }
317              
318             sub constraint_uri_fragment {
319 2     2 0 514 $_[0] =~ m/$path/;
320 2 100 33     21 $2 || $4 || $5 || $6 ? 0 : 1;
321             }
322              
323             sub constraint_uri_query_form {
324 1 50   1 0 34 ref $_[0] eq 'HASH' ? 1 : 0;
325             }
326              
327             sub constraint_uri_escape {
328 2 100   2 0 734 $_[0] =~ m/($esc)(?!$unesc)/ ? 0 : 1;
329             }
330              
331             sub constraint_uri_unescape {
332 2 100   2 0 513 $_[0] =~ m/%$unesc/ ? 0 : 1;
333             }
334              
335             sub _uri {
336 9     9   125 my $hide = sprintf "uri_%s", shift;
337 9         45 \&$hide;
338             }
339              
340             sub uri_schema {
341 1     1 0 10 $_[0] =~ m/$path/;
342 1         16 return $2;
343             }
344              
345             sub uri_host {
346 1     1 0 11 $_[0] =~ m/$path/;
347 1         47 return $4;
348             }
349              
350             sub uri_path {
351 1     1 0 10 $_[0] =~ m/$path/;
352 1         17 return $5;
353             }
354              
355             sub uri_query_string {
356 1     1 0 12 $_[0] =~ m/$path/;
357 1         3 return uri_unescape($6);
358             }
359              
360             sub uri_fragment {
361 1     1 0 11 $_[0] =~ m/$path/;
362 1         19 return $7;
363             }
364              
365             sub uri_query_form {
366 1     1 0 517 $_[0] =~ m/$path/;
367 1         17 my $query_string = uri_unescape($6);
368 1         5 $query_string =~ s,^\?,,;
369             return +{
370 1         35 split '=', $query_string
371             };
372             }
373              
374             sub uri_escape {
375 1     1 0 10 my ($string, %escape) = (shift, %{ $entity{escape} });
  1         228  
376 1         24 $string =~ s/($esc)/$escape{$1}/eg;
  6         17  
377 1         49 $string;
378             }
379              
380             sub uri_unescape {
381 3     3 0 12 my ($string, %unescape) = (shift, %{ $entity{unescape} });
  3         257  
382 3         51 $string =~ s/(%$unesc)/$unescape{$1}/eg;
  6         17  
383 3         96 $string;
384             }
385              
386             $meta->add_type({
387             name => 'Count',
388             parent => scalar $meta->Str,
389             abuse_constraint => \&_count_constraint,
390             abuse => \&_html,
391             coercion => sub {
392             my $ref = ref $_[0];
393             return $ref eq 'ARRAY' ? scalar @{$_[0]} : scalar keys %{$_[0]};
394             },
395             });
396              
397             $meta->add_type({
398             name => 'JSON',
399             parent => scalar $meta->Any,
400             constraint => sub {
401             my $ref = ref $_[0];
402             $ref ? 1 : 0;
403             },
404             coercion => sub {
405             require JSON;
406             my $json = JSON->new;
407             return $json->decode($_[0]);
408             },
409             abuse_parent => \&_json_change,
410             abuse => \&_json
411             });
412              
413             sub _json_change {
414 0     0   0 my $ref = ref $_[0];
415 0 0 0     0 return unless ! $ref || $ref eq 'ARRAY';
416 0 0       0 my $key = $ref eq 'ARRAY' ? $_[0]->[0] : $_[0];
417 0         0 my $type = eval{ $meta->$key };
  0         0  
418 0 0       0 $type ? shift @{$_[0]} : do { $type = $meta->Str if $key eq 'encode'; };
  0 0       0  
  0         0  
419 0         0 $type;
420             }
421              
422             sub _json {
423 0     0   0 require JSON;
424 0         0 my $json = JSON->new;
425 0         0 my $ref = ref $_[0];
426 0 0       0 my $type = $ref ? $_[0]->[0] : $_[0];
427 0 0 0     0 map { $json = $json->$_ } @{ $_[0]->[1] } if ( $ref && ref $_[0]->[1] eq 'ARRAY' );
  0         0  
  0         0  
428 0     0   0 return sub { $json->$type($_[0]) };
  0         0  
429             }
430              
431             sub compare {
432 7     7 0 13 my ($recurse, %same) = shift;
433 7   100     35 $same{reftype $_ || 'MAGIC'}++ for @_;
434 7 100       36 return 0 if scalar keys %same != 1;
435 5         17 return $recurse->{[(keys %same)]->[0]}->($recurse, @_);
436             }
437              
438             sub recurse {
439 14     14 0 25 my ($recurse, $ref) = shift;
440 14   100     47 $ref = reftype($_[0]) || 'MAGIC';
441 14 50       40 $recurse->{$ref}->($recurse, $_[0]) if (exists $recurse->{$ref});
442 14         63 $_[0];
443             }
444              
445             # TODO a little documentations
446             # TBC .....
447              
448             __PACKAGE__->meta->make_immutable;
449              
450             1;
451              
452             __END__