File Coverage

blib/lib/MooseX/Types/MoreUtils.pm
Criterion Covered Total %
statement 116 127 91.3
branch 45 70 64.2
condition 3 15 20.0
subroutine 23 23 100.0
pod 0 3 0.0
total 187 238 78.5


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