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 10     10   868982 use 5.006;
  10         68  
4 10     10   57 use strict;
  10         20  
  10         202  
5 10     10   49 use warnings;
  10         28  
  10         380  
6 10     10   63 use Scalar::Util qw/blessed reftype refaddr/;
  10         20  
  10         834  
7              
8 10     10   4434 use parent 'Types::Standard';
  10         2969  
  10         60  
9            
10             our @EXPORT_OK = ( Types::Standard->type_names );
11              
12             our $meta = __PACKAGE__->meta;
13             our $VERSION = '0.000007';
14              
15             our (%entity, %recurse, %compare, $esc, $unesc, $path);
16             BEGIN {
17 10     10   822056 %entity = (
18             encode => {
19             q{&} => q{&}, q{"} => q{"}, q{'} => q{'}, q{<} => q{<}, q{>} => q{>}
20             }
21             );
22 10         32 my @keys = keys %{$entity{encode}};
  10         58  
23             $entity{decode} = +{ map {
24 10         37 $entity{encode}->{$_} => $_
  50         188  
25             } @keys };
26 10         90 $entity{encode}->{regex} = join "|", map { quotemeta($_) } @keys;
  50         138  
27 10         40 $entity{decode}->{regex} = join "|", map { quotemeta($_) } keys %{$entity{decode}};
  50         112  
  10         38  
28             $entity{escape} = +{
29             map {
30 10         40 chr($_) => sprintf("%%%02X", $_)
  2560         8416  
31             } (0..255)
32             };
33             $entity{unescape} = +{
34             map {
35 2560         6437 $entity{escape}->{$_} => $_
36 10         233 } keys %{$entity{escape}}
  10         269  
37             };
38 10         391 $esc = qr/[^A-Za-z0-9\-\._~]/;
39 10         37 $unesc = qr/[0-9A-Fa-f]{2}/;
40 10         28 $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 10         115 );
46             %compare = (
47             ARRAY => sub {
48 2         5 my $recurse = shift;
49 2         5 my @length = sort { $a < $b } map { scalar @{ $_ } } (@_);
  2         8  
  4         6  
  4         13  
50 2 100       9 for my $i (0 .. $length[0] - 1) { compare($recurse, map { $_->[$i] } @_) or return 0; }
  3         6  
  6         16  
51 1         14 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         6  
  3         4  
  6         13  
  3         23  
62 10         530 );
63             }
64             {
65             # all powerfull
66 10     10   112 no strict 'refs';
  10         33  
  10         36525  
67             my $counter = 0;
68             *{"Type::Tiny::by"} = sub {
69 28     28   853 my ($pn, $parent, $hide, $act) = ($_[0]->name, shift, shift);
70 28 100   18   591 $act = ref $hide ? sub { compare(\%compare, @_) } : sub { $_[0] =~ m/$_[1]/; };
  5         117  
  75         1827  
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 28   33     54 ($parent->{abuse_constraint} ? (constraint => $parent->{abuse_constraint}->($hide)) : ())
82             });
83 26         22544 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   43 my $hide = shift;
95             return sub {
96 5 50   5   32853 defined $_[0] ? [split $hide, $_[0]] : $_[0];
97             }
98 3         37 }
99              
100             $meta->add_type({
101             name => 'StrToHash',
102             parent => scalar $meta->HashRef,
103             abuse => \&_strToHash
104             });
105              
106             sub _strToHash {
107 3     3   41 my $hide = shift;
108             return sub {
109 3 50   3   35285 defined $_[0] ? +{split $hide, $_[0]} : $_[0];
110             }
111 3         40 }
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 7 my ($sr) = (quotemeta($_[0][0]));
122             return sub {
123 6     6   26199 my $str = shift;
124 6         46 ! ($str =~ m/$sr/);
125 2         17 };
126             }
127              
128             sub search_replace {
129 2     2 0 34 my (@sr) = (quotemeta($_[0][0]), $_[0][1]);
130             return sub {
131 3     3   26 my $str = shift;
132 3 50       50 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         65 return $str;
136 2         18 };
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   67 my $hide = sprintf "array_to_hash_%s", shift;
154 5         43 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 733 my @array = @{$_[0]};
  1         4  
160 1         2 my %hash;
161 1         4 while (@array) {
162 2         6 my ($even, $odd) = (shift @array, shift @array);
163 2         9 $hash{$odd} = $even
164             }
165 1         20 return \%hash;
166             }
167              
168             sub array_to_hash_odd {
169 1     1 0 729 my @array = @{$_[0]};
  1         4  
170 1         3 return +{ (map {$array[$_]} grep {$_ & 1} 1 .. scalar @array - 1) };
  2         26  
  3         6  
171             }
172              
173             sub array_to_hash_even {
174 1     1 0 933 my @array = @{$_[0]};
  1         4  
175 1         5 return +{ (map {$array[$_]} grep {not $_ & 1} 0 .. scalar @array - 1) };
  2         27  
  4         10  
176             }
177              
178             sub array_to_hash_flat {
179 1     1 0 738 return +{ _flat($_[0]) };
180             }
181              
182             sub array_to_hash_merge {
183             return +{
184 1     1 0 751 map { %{$_} } grep { ref $_ eq 'HASH' } @{$_}
  2         4  
  2         26  
  2         8  
  1         5  
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   51 my $hide = sprintf ('hash_to_array_%s', shift);
201 3         35 \&$hide;
202             }
203              
204             sub hash_to_array_keys {
205 1     1 0 772 return [ sort keys %{ $_[0] } ];
  1         27  
206             }
207              
208             sub hash_to_array_values {
209 1     1 0 845 return [ sort values %{ $_[0] } ];
  1         26  
210             }
211              
212             sub hash_to_array_flat {
213 1     1 0 1080 return [_flat($_[0])];
214             }
215              
216             sub _flat {
217 2     2   6 my @lazy;
218             my %r = (
219 4     4   8 ARRAY => sub { map { recurse($_[0], $_) } @{ $_[1] } },
  6         16  
  4         13  
220 2 50   2   6 HASH => sub { do { recurse($_[0], $_) && recurse($_[0], $_[1]->{$_}); } for sort keys %{ $_[1] }; },
  2         14  
  3         11  
221 0     0   0 SCALAR => sub { recurse($_[0], ${$_[1]}) },
  0         0  
222 8     8   19 MAGIC => sub { push @lazy, $_[1] },
223 2         46 );
224 2         14 recurse(\%r, $_[0]);
225 2         75 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   8 my $hide = sprintf('constraint_%s', shift);
237 2         15 \&$hide;
238             }
239              
240             sub _html {
241 2     2   29 my $hide = sprintf('%s', shift);
242 2         15 \&$hide;
243             }
244              
245             sub constraint_encode_entity {
246 2     2 0 645 my ($str, %encode) = (shift, %{ $entity{encode} });
  2         18  
247 2 100       58 $str =~ m/($encode{regex})(?![a-z#]+;)/ ? 0 : 1;
248             }
249              
250             sub encode_entity {
251 1     1 1 10 my ($str, %encode) = (shift, %{ $entity{encode} });
  1         5  
252 1         25 $str =~ s/($encode{regex})/$encode{$1}/eg;
  7         26  
253 1         23 return $str;
254             }
255              
256             sub constraint_decode_entity {
257 2 100   2 0 25275 shift =~ m/&([a-z#]+;)/ ? 0 : 1;
258             }
259              
260             sub decode_entity {
261 1     1 1 9 my ($str, %decode) = (shift, %{ $entity{decode} });
  1         11  
262 1         47 $str =~ s/($decode{regex})/$decode{$1}/eg;
  7         68  
263 1         30 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 8     8   54 my $hide = shift;
288 8 100       63 return scalar $meta->Str if $hide =~ m/^escape|unescape|schema|host|path|query_string|fragment$/;
289 1         6 return scalar $meta->HashRef;
290             }
291              
292             sub _uri_constraint {
293 8     8   20 my $hide = sprintf "constraint_uri_%s", shift;
294 8         49 \&$hide;
295             }
296              
297             # I don't know why, just don't ask
298             sub constraint_uri_schema {
299 2     2 0 676 $_[0] =~ m/$path/;
300 2 100 33     22 $4 || $5 || $6 || $7 ? 0 : 1;
301             }
302              
303             sub constraint_uri_host {
304 2     2 0 647 $_[0] =~ m/$path/;
305 2 100 33     19 $2 || $5 || $6 || $7 ? 0 : 1;
306             }
307              
308             sub constraint_uri_path {
309 2     2 0 649 $_[0] =~ m/$path/;
310 2 100 33     20 $2 || $4 || $6 || $7 ? 0 : 1;
311             }
312              
313             sub constraint_uri_query_string {
314 2     2 0 666 $_[0] =~ m/$path/;
315 2 100 33     32 $2 || $4 || $5 || $7 ? 0 : 1;
316             }
317              
318             sub constraint_uri_fragment {
319 2     2 0 717 $_[0] =~ m/$path/;
320 2 100 33     25 $2 || $4 || $5 || $6 ? 0 : 1;
321             }
322              
323             sub constraint_uri_query_form {
324 1 50   1 0 37 ref $_[0] eq 'HASH' ? 1 : 0;
325             }
326              
327             sub constraint_uri_escape {
328 2 100   2 0 710 $_[0] =~ m/($esc)(?!$unesc)/ ? 0 : 1;
329             }
330              
331             sub constraint_uri_unescape {
332 2 100   2 0 677 $_[0] =~ m/%$unesc/ ? 0 : 1;
333             }
334              
335             sub _uri {
336 8     8   131 my $hide = sprintf "uri_%s", shift;
337 8         40 \&$hide;
338             }
339              
340             sub uri_schema {
341 1     1 0 12 $_[0] =~ m/$path/;
342 1         21 return $2;
343             }
344              
345             sub uri_host {
346 1     1 0 13 $_[0] =~ m/$path/;
347 1         21 return $4;
348             }
349              
350             sub uri_path {
351 1     1 0 12 $_[0] =~ m/$path/;
352 1         20 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 13 $_[0] =~ m/$path/;
362 1         21 return $7;
363             }
364              
365             sub uri_query_form {
366 1     1 0 630 $_[0] =~ m/$path/;
367 1         4 my $query_string = uri_unescape($6);
368 1         6 $query_string =~ s,^\?,,;
369             return +{
370 1         28 split '=', $query_string
371             };
372             }
373              
374             sub uri_escape {
375 1     1 0 10 my ($string, %escape) = (shift, %{ $entity{escape} });
  1         243  
376 1         33 $string =~ s/($esc)/$escape{$1}/eg;
  6         21  
377 1         50 $string;
378             }
379              
380             sub uri_unescape {
381 3     3 0 13 my ($string, %unescape) = (shift, %{ $entity{unescape} });
  3         395  
382 3         218 $string =~ s/(%$unesc)/$unescape{$1}/eg;
  6         56  
383 3         118 $string;
384             }
385              
386             $meta->add_type({
387             name => 'Count',
388             parent => scalar $meta->Str,
389             coercion => sub {
390             my $ref = ref $_[0];
391             return $ref eq 'ARRAY' ? scalar @{$_[0]} : scalar keys %{$_[0]};
392             },
393             });
394              
395             $meta->add_type({
396             name => 'JSON',
397             parent => scalar $meta->Any,
398             constraint => sub {
399             my $ref = ref $_[0];
400             $ref ? 1 : 0;
401             },
402             coercion => sub {
403             require JSON;
404             my $json = JSON->new;
405             return $json->decode($_[0]);
406             },
407             abuse_parent => \&_json_change,
408             abuse => \&_json
409             });
410              
411             sub _json_change {
412 0     0   0 my $ref = ref $_[0];
413 0 0 0     0 return unless ! $ref || $ref eq 'ARRAY';
414 0 0       0 my $key = $ref eq 'ARRAY' ? $_[0]->[0] : $_[0];
415 0         0 my $type = eval{ $meta->$key };
  0         0  
416 0 0       0 $type ? shift @{$_[0]} : do { $type = $meta->Str if $key eq 'encode'; };
  0 0       0  
  0         0  
417 0         0 $type;
418             }
419              
420             sub _json {
421 0     0   0 require JSON;
422 0         0 my $json = JSON->new;
423 0         0 my $ref = ref $_[0];
424 0 0       0 my $type = $ref ? $_[0]->[0] : $_[0];
425 0 0 0     0 map { $json = $json->$_ } @{ $_[0]->[1] } if ( $ref && ref $_[0]->[1] eq 'ARRAY' );
  0         0  
  0         0  
426 0     0   0 return sub { $json->$type($_[0]) };
  0         0  
427             }
428              
429             sub compare {
430 8     8 0 17 my ($recurse, %same) = shift;
431 8   100     46 $same{reftype $_ || 'MAGIC'}++ for @_;
432 8 100       50 return 0 if scalar keys %same != 1;
433 5         21 return $recurse->{[(keys %same)]->[0]}->($recurse, @_);
434             }
435              
436             sub recurse {
437 14     14 0 27 my ($recurse, $ref) = shift;
438 14   100     69 $ref = reftype($_[0]) || 'MAGIC';
439 14 50       62 $recurse->{$ref}->($recurse, $_[0]) if (exists $recurse->{$ref});
440 14         33 $_[0];
441             }
442              
443             # TODO a little documentations
444             # TBC .....
445              
446             __PACKAGE__->meta->make_immutable;
447              
448             1;
449              
450             __END__