File Coverage

blib/lib/MooseX/Types/MoreUtils.pm
Criterion Covered Total %
statement 115 126 91.2
branch 45 70 64.2
condition 3 15 20.0
subroutine 23 23 100.0
pod 0 3 0.0
total 186 237 78.4


line stmt bran cond sub pod time code
1 17     17   5165959 use 5.008001;
  17         54  
  17         598  
2 17     17   71 use strict;
  17         25  
  17         439  
3 17     17   63 use warnings;
  17         25  
  17         971  
4              
5             package MooseX::Types::MoreUtils;
6              
7             our $AUTHORITY = 'cpan:TOBYINK';
8             our $VERSION = '0.001';
9              
10 17     17   76 use Carp 0 qw( carp croak );
  17         266  
  17         1141  
11 17     17   86 use List::Util 1.29 qw( pairkeys pairvalues pairmap pairgrep );
  17         359  
  17         1346  
12 17     17   85 use Scalar::Util 1.23 qw( blessed reftype );
  17         276  
  17         3006  
13              
14             sub _reify
15             {
16 199     199   1365 my $type = shift;
17            
18 199 100       424 if (ref $type)
19             {
20 163 100       634 return $type if blessed($type);
21 8 100       47 return _where('Any', $type) if reftype($type) eq 'CODE';
22            
23 6 50       14 if (ref $type eq 'HASH')
24             {
25 6         13 my ($key, $value) = each(%$type);
26 6 50       16 if (1 == keys(%$type))
27             {
28 6         23 my $function = +{
29             role => 'role_type',
30             duck => 'duck_type',
31             class => 'class_type',
32             union => 'union',
33             enum => 'enum',
34             }->{$key};
35            
36 6 100       14 if ($function eq 'union')
37             {
38 1         3 $value = [ map _reify($_), @$value ];
39             }
40            
41 6 50       511 if ($function)
42             {
43 6         29 require Moose::Util::TypeConstraints;
44             package Moose::Util::TypeConstraints;
45 17     17   78 no strict qw(refs);
  17         30  
  17         17924  
46 6         27 return &$function($value);
47             }
48             }
49             }
50             }
51             else
52             {
53 36         172 require Moose::Util::TypeConstraints;
54 36         116 my $obj = Moose::Util::TypeConstraints::find_or_create_type_constraint($type);
55 36 50       5117 return $obj if blessed($obj);
56             }
57            
58 0         0 croak("Value '$type' does not seem to be a type constraint; stopped");
59             }
60              
61             sub _codify
62             {
63 58     58   85 my $code = shift;
64            
65 58 100       226 if (ref $code)
66             {
67 22 50       144 return $code if reftype($code) eq 'CODE';
68 0 0 0     0 return \&$code if $INC{'overload.pm'} && overload::Method($code, '&{}');
69             }
70             else
71             {
72 36 100       1591 my $sub = exists(&Sub::Quote::quote_sub)
73             ? Sub::Quote::quote_sub($code)
74             : scalar eval qq{ sub { $code } };
75 36 50 33     1029 $sub && reftype($sub) eq 'CODE'
76             or croak("Could not compile '$code' into a sub; stopped");
77 36         127 return $sub;
78             }
79            
80 0         0 croak("Value '$code' does not seem to be a code ref; stopped");
81             }
82              
83             sub _clone
84             {
85 39     39   71 my $self = _reify(shift);
86 39         756 my @args = ( name => $self->name );
87 39 50       4255 push @args, (message => $self->message)
88             if $self->has_message;
89 39         3487 $self->create_child_type(@args);
90             }
91              
92             sub _plus_coercions :method
93             {
94 36     36   501909 my $self = _reify(shift);
95            
96             my @new_coercions = pairmap {
97 54     54   161 _reify($a) => _codify($b);
98 36         433 } @_;
99            
100 36 100       274 return $self->plus_coercions(@new_coercions)
101             if $self->can('plus_coercions');
102            
103 24 100       4238 push @new_coercions, @{ $self->coercion->type_coercion_map }
  6         377  
104             if $self->has_coercion;
105            
106 24         3669 my $new = _clone($self);
107 24 50       35458 if (@new_coercions)
108             {
109 24 50       151 my $class = $new->isa('Type::Tiny')
110             ? 'Type::Coercion'
111             : 'Moose::Meta::TypeCoercion';
112 24 50       1171 eval "require $class" or die($@);
113 24 50       737 $new->coercion($class->new) unless $new->has_coercion;
114 24         8726 $new->coercion->add_type_coercions(@new_coercions);
115             }
116 24         14034 return $new;
117             }
118              
119             sub _minus_coercions :method
120             {
121 9     9   1925 my $self = _reify(shift);
122 9         28 my @not = map _reify($_), @_;
123            
124             my @keep = pairgrep {
125 18     18   418 my $keep_this = 1;
126 18         30 NOT: for my $n (@not)
127             {
128 18 100       34 _reify($a)->equals($n) or next NOT;
129 9         1673 $keep_this = 0;
130 9         16 last NOT;
131             }
132 18         2988 $keep_this;
133 9 50       39 } @{ $self->has_coercion ? $self->coercion->type_coercion_map : [] };
  9         192  
134            
135 9         45 my $new = _clone($self);
136 9 50       10298 if (@keep)
137             {
138 9 100       43 my $class = $new->isa('Type::Tiny')
139             ? 'Type::Coercion'
140             : 'Moose::Meta::TypeCoercion';
141 9 50       472 eval "require $class" or die($@);
142 9 50       192 $new->coercion($class->new) unless $new->has_coercion;
143 9         1949 $new->coercion->add_type_coercions(@keep);
144             }
145 9         2325 return $new;
146             }
147              
148             sub _no_coercions :method
149             {
150 9     9   2054 my $self = _reify(shift);
151 9 100       47 return $self->no_coercions if $self->can('no_coercions');
152 6         10 return _clone($self);
153             }
154              
155             sub _of :method
156             {
157 6     6   3008 my $self = _reify(shift);
158 6 50       50 $self->can('parameterize')
159             or croak('This type constraint cannot be parameterized; stopped');
160 6         834 return $self->parameterize(map _reify($_), @_);
161             }
162              
163             sub _where :method
164             {
165 5     5   528 my $self = _reify(shift);
166 5 100       35 return $self->where(@_) if $self->can('where');
167            
168 4         563 my $code = _codify($_[0]);
169 4         20 return $self->create_child_type(constraint => $code);
170             }
171              
172             sub _type :method
173             {
174 6     6   29200 _reify(shift);
175             }
176              
177             sub subs :method
178             {
179 51     51 0 511 '$_plus_coercions' => \&_plus_coercions,
180             '$_minus_coercions' => \&_minus_coercions,
181             '$_no_coercions' => \&_no_coercions,
182             '$_of' => \&_of,
183             '$_where' => \&_where,
184             '$_type' => \&_type,
185             }
186              
187             sub sub_names :method
188             {
189 34     34 0 45 my $me = shift;
190 34         57 pairkeys($me->subs);
191             }
192              
193             sub setup_for :method
194             {
195 17     17 0 236 my $me = shift;
196 17         43 my @refs = @_;
197 17         68 my @subs = pairvalues($me->subs);
198            
199 17         70 while (@refs)
200             {
201 102         103 my $ref = shift(@refs);
202 102         90 my $sub = shift(@subs);
203 102 50       178 die "Internal problem" unless ref($sub) eq 'CODE';
204            
205 102         93 $$ref = $sub;
206 102 50       294 &Internals::SvREADONLY($ref, 1) if exists(&Internals::SvREADONLY);
207             }
208            
209 17 50       54 die "Internal problem" if @subs;
210 17         43 return;
211             }
212              
213             sub import :method
214             {
215 17     17   229 my $me = shift;
216 17         34 my (%args) = @_;
217 17         43 my ($caller, $file) = caller;
218            
219 17 50       73 $args{magic} = "auto" unless defined $args{magic};
220            
221 17 50 33     134 if ($file ne '-e'
      33        
222             and $args{magic}
223 17         7336 and eval { require B::Hooks::Parser })
224             {
225 17         26331 my $varlist = join ',', $me->sub_names;
226 17         51 my $reflist = join ',', map "\\$_", $me->sub_names;
227 17         90 B::Hooks::Parser::inject(";my($varlist);$me\->setup_for($reflist);");
228 17         13508 return;
229             }
230            
231 0 0 0       if ($args{magic} and $args{magic} ne "auto")
232             {
233 0           carp(__PACKAGE__ . " could not use magic; continuing regardless");
234             }
235            
236 0           my %subs = $me->subs;
237 0           for my $sub_name (sort keys %subs)
238             {
239 0           my $code = $subs{$sub_name};
240 0           $sub_name =~ s/^.//;
241 17     17   90 no strict 'refs';
  17         26  
  17         1238  
242 0           *{"$caller\::$sub_name"} = \$code;
  0            
243             }
244             }
245              
246             1;
247              
248             __END__
249              
250             =pod
251              
252             =encoding utf-8
253              
254             =head1 NAME
255              
256             MooseX::Types::MoreUtils - utility methods to apply to Moose type constraints
257              
258             =head1 SYNOPSIS
259              
260             {
261             package Spruce;
262            
263             use Moose;
264             use MooseX::Types::Moose qw(ArrayRef Str);
265             use MooseX::Types::MoreUtils;
266             use Local::TextUtils qw( csv_to_arrayref );
267            
268             has goose => (
269             is => 'ro',
270             isa => ArrayRef->$_plus_coercions( Str, \&csv_to_arrayref ),
271             coerce => 1,
272             );
273             }
274              
275             =head1 DESCRIPTION
276              
277             This module provides a bunch of methods for working with Moose type
278             constraints, which it exposes as lexical coderef variables. (Like
279             L<Object::Util>.)
280              
281             See L<Object::Util/"Rationale">.
282              
283             =head2 Methods
284              
285             The invocants for these methods are type constraints. These may be
286             L<Moose::Meta::TypeConstraint>, L<MooseX::Types::TypeDecorator>, or
287             L<Type::Tiny> objects. As a convenience, strings are also accepted,
288             which will be looked up via Moose's C<find_or_create_type_constraint>
289             utility function. Various other conveniences are provided; see
290             L</"Shortcuts for type constraints">.
291              
292             =head3 Constraint manipulation
293              
294             =over
295              
296             =item C<< $_where >>
297              
298             Creates an anonymous subtype with an additional constraint. For example
299             to create a type constraint that accepts odd-numbered integers, you
300             could use:
301              
302             isa => Int->$_where(sub { $_ % 2 })
303              
304             Alternatively the coderef can be replaced with a string of Perl code:
305              
306             isa => Int->$_where('$_ % 2')
307              
308             =item C<< $_of >>
309              
310             Can be used to parameterize type constraints. For example, for an
311             arrayref of odd integers:
312              
313             isa => ArrayRef->$_of( Int->$_where('$_ % 2') )
314              
315             Or if you'd prefer, an arrayref of integers, where the arrayref
316             contains an odd number of items:
317              
318             isa => ArrayRef->$_of(Int)->$_where('@$_ % 2')
319              
320             =item C<< $_type >>
321              
322             The identity function. C<< Int->$_type >> just returns C<Int>.
323              
324             This is occasionally useful if you're taking advantage of the fact that
325             the invocant doesn't have to be a I<real> type constraint but can
326             instead use a L<shortcut|/"Shortcuts for type constraints">. In these
327             cases it's not quite the identity, because it returns a real type
328             constraint object.
329              
330             =back
331              
332             =head3 Coercion manipulation
333              
334             =over
335              
336             =item C<< $_plus_coercions >>
337              
338             Given an existing type constraint, creates a new child type with some
339             extra coercions.
340              
341             isa => ArrayRef->$_plus_coercions(
342             Str, \&csv_to_arrayref,
343             "HashRef", sub { [ values(%$_) ] },
344             ),
345             coerce => 1,
346              
347             =item C<< $_minus_coercions >>
348              
349             Given an existing type constraint, creates a new child type with fewer
350             coercions.
351              
352             use MooseX::Types::Moose qw( HashRef );
353             use MooseX::Types::URI qw( Uri );
354            
355             # Don't want to coerce from HashRef,
356             # but keep the coercion from Str.
357             #
358             isa => Uri->$_minus_coercions(HashRef)
359              
360             =item C<< $_no_coercions >>
361              
362             Given an existing type constraint, creates a new child type with no
363             coercions at all.
364              
365             isa => Uri->$_no_coercions
366              
367             As above, it's just equivalent to C<< coerce => 0 >> so might seem a
368             bit useless. But it is handy when chained with C<< $_plus_coercions >>
369             to provide a stable base to build your coercions on:
370              
371             # This doesn't just create a type like Uri but
372             # with extra coercions; it explicitly ignores any
373             # coercions that were already attached to Uri.
374             #
375             isa => Uri->$_no_coercions->$_plus_coercions(
376             Str, sub { ... }
377             );
378              
379             =back
380              
381             =head2 Shortcuts for type constraints
382              
383             Where type constraints are expected by this module, you can take some
384             shortcuts. Strings are passed to C<find_or_create_type_constraint>
385             for example, meaning that the following two exampes are identical:
386              
387             With MooseX::Types...
388              
389             use MooseX::Types::Moose qw( ArrayRef Str );
390             ArrayRef->$_plus_coercions( Str, \&csv_to_arrayref );
391              
392             Without MooseX::Types...
393              
394             "ArrayRef"->$_plus_coercions( "Str", \&csv_to_arrayref );
395              
396             If, instead of a type constraint you give a coderef, this will be
397             converted into a subtype of C<Any>.
398              
399             You may also give a hashref with a single key-value pair, such as:
400              
401             { class => "Some::Class::Name" }
402             { role => "Some::Role::Name" }
403             { duck => \@method_names }
404             { union => \@type_constraints }
405             { enum => \@strings }
406              
407             These do what I think you'd expect them to do.
408              
409             =head1 CAVEATS
410              
411             This module does not remove the need for C<< coerce => 1 >>!
412              
413             =head1 BUGS
414              
415             Please report any bugs to
416             L<http://rt.cpan.org/Dist/Display.html?Queue=MooseX-Types-MoreUtils>.
417              
418             =head1 SEE ALSO
419              
420             If you use L<Types::Standard>, this module is fairly redundant, as
421             these features and shortcuts are mostly built-in!
422              
423             =head1 AUTHOR
424              
425             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
426              
427             =head1 COPYRIGHT AND LICENCE
428              
429             This software is copyright (c) 2014 by Toby Inkster.
430              
431             This is free software; you can redistribute it and/or modify it under
432             the same terms as the Perl 5 programming language system itself.
433              
434             =head1 DISCLAIMER OF WARRANTIES
435              
436             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
437             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
438             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
439              
440             =cut