File Coverage

blib/lib/MooseX/MungeHas.pm
Criterion Covered Total %
statement 137 171 80.1
branch 33 70 47.1
condition 12 24 50.0
subroutine 17 19 89.4
pod n/a
total 199 284 70.0


line stmt bran cond sub pod time code
1             package MooseX::MungeHas;
2              
3 5     5   1410640 use 5.008;
  5         20  
4 5     5   26 use strict;
  5         10  
  5         86  
5 5     5   26 use warnings;
  5         9  
  5         194  
6              
7             BEGIN {
8 5     5   15 $MooseX::MungeHas::AUTHORITY = 'cpan:TOBYINK';
9 5         113 $MooseX::MungeHas::VERSION = '0.008';
10             };
11              
12 5     5   26 use Carp qw(croak);
  5         10  
  5         291  
13 5     5   28 use Scalar::Util qw(blessed);
  5         8  
  5         464  
14              
15             BEGIN {
16 5     5   19 for my $backend (qw/ Eval::TypeTiny Eval::Closure /)
17             {
18             last if eval(
19 5 50       312 "require $backend; *eval_closure = \\&$backend\::eval_closure;"
20             );
21             }
22 5 50       144 exists(&eval_closure)
23             or croak "Could not load Eval::TypeTiny";
24             };
25              
26             sub import
27             {
28 5     5   35 no strict qw(refs);
  5         10  
  5         521  
29            
30 4     4   34 my $class = shift;
31 4         10 my $caller = caller;
32            
33 4 50       7 my $orig = \&{"$caller\::has"}
  4         24  
34             or croak "$caller does not have a 'has' function to munge";
35            
36             my %export = (@_ == 1 and ref($_[0]) eq "HASH")
37 4 100 100     34 ? %{ $_[0] }
  1         4  
38             : ( has => [@_] );
39            
40 4         17 for my $f (sort keys %export)
41             {
42 5     5   36 no warnings qw(redefine prototype);
  5         10  
  5         5463  
43            
44 5         6949 *{"$caller\::$f"} = $class->_make_has(
45             $caller,
46 5         13 $class->_make_munger($caller, @{$export{$f}}),
  5         17  
47             $orig,
48             );
49             }
50             }
51              
52             {
53             sub __detect_oo
54             {
55 4     4   8 my $package = $_[0];
56            
57 4 100       13 if ($INC{'Moo.pm'})
58             {
59 1 50       8 return "Moo" if $Moo::MAKERS{$package};
60 0 0       0 return "Moo" if $Moo::Role::INFO{$package};
61             }
62            
63 3 50       12 if ($INC{'Moose.pm'})
64             {
65 3         16 require Moose::Util;
66 3 50       11 return "Moose" if Moose::Util::find_meta($package);
67             }
68            
69 0 0       0 if ($INC{'Mouse.pm'})
70             {
71 0         0 require Mouse::Util;
72 0 0       0 return "Mouse" if Mouse::Util::find_meta($package);
73             }
74            
75 0         0 my $meta;
76 0 0       0 eval { $meta = $package->meta } or return "?";
  0         0  
77            
78 0 0       0 return "Moo" if ref($meta) eq "Moo::HandleMoose::FakeMetaClass";
79 0 0       0 return "Mouse" if $meta->isa("Mouse::Meta::Module");
80 0 0       0 return "Moose" if $meta->isa("Moose::Meta::Class");
81 0 0       0 return "Moose" if $meta->isa("Moose::Meta::Role");
82 0         0 return "?";
83             }
84            
85             my %_cache;
86 24   66 24   101 sub _detect_oo { $_cache{$_[0]} ||= __detect_oo(@_) };
87             }
88              
89             sub _make_munger
90             {
91 5     5   9 my $class = shift;
92 5         18 return $class->_compile_munger_code(@_);
93             }
94              
95             sub _compile_munger_code
96             {
97 5     5   10 my $class = shift;
98 5         13 my ($caller, @features) = @_;
99 5         24 my %features = map +($_ => 1), grep !ref, @features;
100 5         13 my @subs = grep ref, @features;
101            
102 5         10 my @code = "sub {";
103            
104 5 100       13 if (_detect_oo($caller) =~ /^Mo[ou]se$/)
105             {
106 4         64 push @code, ' if (exists($_{isa}) && !ref($_{isa})) {';
107 4         10 push @code, ' $_{isa} = '._detect_oo($caller).'::Util::TypeConstraints::find_or_create_isa_type_constraint($_{isa});';
108 4         10 push @code, ' }';
109             }
110            
111 5         11 for my $is (qw/ro rw rwp lazy bare/)
112             {
113 25 100       65 if (delete $features{"is_$is"})
114             {
115 5         14 push @code, ' $_{is} ||= "'.$is.'";';
116             }
117             }
118            
119 5         11 push @code, ' if (exists($_{_is})) {';
120 5         9 push @code, ' my $_is = delete($_{_is});';
121 5         10 push @code, ' $_{is} ||= $_is;';
122 5         8 push @code, ' }';
123            
124 5         11 push @code, ' if (ref($_{builder}) eq q(CODE)) {';
125 5         9 push @code, ' no strict qw(refs);';
126 5         8 push @code, ' require Sub::Util;';
127 5         8 push @code, ' my $name = "$_{__CALLER__}::_build_$_";';
128 5         11 push @code, ' *$name = Sub::Util::set_subname($name, $_{builder});';
129 5         8 push @code, ' $_{builder} = $name;';
130 5         10 push @code, ' }';
131            
132 5 100       9 unless (_detect_oo($caller) eq "Moo")
133             {
134 4         8 push @code, ' if ($_{is} eq q(lazy)) {';
135 4         7 push @code, ' $_{is} = "ro";';
136 4         10 push @code, ' $_{lazy} = 1 unless exists($_{lazy});';
137 4         7 push @code, ' $_{builder} = "_build_$_" if $_{lazy} && !exists($_{builder}) && !exists($_{default});';
138 4         6 push @code, ' }';
139            
140 4         7 push @code, ' if ($_{is} eq q(rwp)) {';
141 4         12 push @code, ' $_{is} = "ro";';
142 4         8 push @code, ' $_{writer} = "_set_$_" unless exists($_{writer});';
143 4         4 push @code, ' }';
144            
145 4         7 push @code, ' if (ref($_{isa}) eq q(CODE)) {';
146 4         7 push @code, ' require Type::Tiny;';
147 4         7 push @code, ' my $code = $_{isa};';
148 4         7 push @code, ' my $safe = sub { !!eval { $code->($_); 1 } };';
149 4         7 push @code, ' $_{isa} = "Type::Tiny"->new(constraint => $safe);';
150 4         18 push @code, ' }';
151             }
152            
153 5         15 delete $features{"eq_1"};
154 5         8 push @code, ' my ($pfx, $name) = ($_ =~ /^(_*)(.+)$/);';
155 5         9 push @code, ' $_{builder} = "_build_$_" if exists($_{builder}) && $_{builder} eq q(1);';
156 5         10 push @code, ' $_{clearer} = "${pfx}clear_${name}" if exists($_{clearer}) && $_{clearer} eq q(1);';
157 5         14 push @code, ' $_{predicate} = "${pfx}has_${name}" if exists($_{predicate}) && $_{predicate} eq q(1);';
158 5         16 push @code, ' if (exists($_{trigger}) && $_{trigger} eq q(1)) {';
159 5         9 push @code, ' my $method = "_trigger_$_";';
160 5         10 push @code, ' $_{trigger} = sub { shift->$method(@_) };';
161 5         5 push @code, ' }';
162            
163 5 100       61 if (delete $features{"always_coerce"})
164             {
165 2         5 push @code, ' if (exists($_{isa}) and !exists($_{coerce}) and Scalar::Util::blessed($_{isa}) and $_{isa}->can("has_coercion") and $_{isa}->has_coercion) {';
166 2         3 push @code, ' $_{coerce} = 1;';
167 2         4 push @code, ' }';
168             }
169            
170 5 100       12 if (_detect_oo($caller) eq "Moo")
171             {
172 1         2 push @code, ' if (defined($_{coerce}) and !ref($_{coerce}) and $_{coerce} eq "1") {';
173 1         1 push @code, ' Scalar::Util::blessed($_{isa})';
174 1         2 push @code, ' and $_{isa}->isa("Type::Tiny")';
175 1         1 push @code, ' and ($_{coerce} = $_{isa}->coercion);';
176 1         2 push @code, ' }';
177 1         2 push @code, ' elsif (exists($_{coerce}) and not $_{coerce}) {';
178 1         1 push @code, ' delete($_{coerce});';
179 1         2 push @code, ' }';
180             }
181            
182 5 50       17 if (delete $features{"no_isa"})
183             {
184 0         0 push @code, ' delete($_{isa}) if !exists($_{coerce});';
185             }
186            
187 5 100       18 if (delete $features{"simple_isa"})
188             {
189 2         5 push @code, ' $_{isa} = "'.$class.'"->_simplify_isa($_{isa}) if Scalar::Util::blessed($_{isa}) && !$_{coerce};';
190             }
191            
192 5         21 push @code, sprintf(' $subs[%d]->(@_);', $_) for 0..$#subs;
193             #push @code, ' ::diag(::explain($_, \%_));';
194 5         10 push @code, ' delete($_{__CALLER__});';
195 5         9 push @code, "}";
196            
197 5 50       15 croak sprintf("Did not understand mungers: %s", join(q[, ], sort keys %features))
198             if keys %features;
199            
200 5         23 return eval_closure(
201             source => \@code,
202             environment => { '@subs' => \@subs },
203             );
204             }
205              
206             sub _simplify_isa
207             {
208 2     2   75 my $class = shift;
209 2         5 my ($t) = @_;
210            
211 2         12 until ($t->can_be_inlined)
212             {
213 4 50       86 if ($t->has_parent)
214             {
215 4         21 $t = $t->parent;
216 4         17 next;
217             }
218            
219 0 0       0 if ($t->isa("Type::Tiny::Intersection"))
220             {
221 0         0 require Type::Tiny::Intersection;
222 0         0 my (@can_be_inlined) = grep $_->can_be_inlined, @$t;
223 0         0 $t = "Type::Tiny::Intersection"->new(type_constraints => \@can_be_inlined);
224 0         0 next;
225             }
226            
227 0         0 require Type::Tiny;
228 0         0 return "Type::Tiny"->new;
229             }
230            
231 2         60 return $t;
232             }
233              
234             sub _make_has
235             {
236 5     5   2951 my $class = shift;
237 5         14 my ($caller, $coderef, $orig) = @_;
238            
239 5 50       11 return $class->_make_has_mouse(@_) if _detect_oo($caller) eq "Mouse";
240            
241             return sub
242             {
243             my ($attr, %spec) = (
244             (@_ == 2 and ref($_[1]) eq "CODE") ? ($_[0], _is => "ro", lazy => 1, builder => $_[1]) :
245 15 100 100 15   22154 (@_ == 2 and ref($_[1]) eq "HASH") ? ($_[0], %{$_[1]}) :
  0 50 66     0  
    100 66        
246             (@_ == 2 and blessed($_[1]) and $_[1]->can('check')) ? ($_[0], _is => "ro", isa => $_[1]) :
247             @_
248             );
249            
250 15         130 $spec{"__CALLER__"} = $caller;
251            
252 15 100       45 if (ref($attr) eq q(ARRAY))
253             {
254 1         4 my @attrs = @$attr;
255 1         3 for my $attr (@attrs)
256             {
257 2         283 local %_ = %spec;
258 2         5 local $_ = $attr;
259 2         48 $coderef->($attr, %_);
260 2         110 $orig->($attr, %_);
261             }
262             }
263             else
264             {
265 14         60 local %_ = %spec;
266 14         34 local $_ = $attr;
267 14         343 $coderef->($attr, %_);
268 14         1061 return $orig->($attr, %_);
269             }
270 5         21 };
271             }
272              
273             sub _make_has_mouse
274             {
275 0     0     my $class = shift;
276 0           my ($caller, $coderef, $orig) = @_;
277            
278             return sub
279             {
280             my ($attr, %spec) = (
281             (@_ == 2 and ref($_[1]) eq "CODE") ? ($_[0], _is => "ro", lazy => 1, builder => $_[1]) :
282 0 0 0 0     (@_ == 2 and ref($_[1]) eq "HASH") ? ($_[0], %{$_[1]}) :
  0 0 0        
    0 0        
283             (@_ == 2 and blessed($_[1]) and $_[1]->can('check')) ? ($_[0], _is => "ro", isa => $_[1]) :
284             @_
285             );
286            
287 0           $spec{"__CALLER__"} = $caller;
288            
289 0 0         if (ref($attr) eq q(ARRAY))
290             {
291 0           croak "MooseX::MungeHas does not support has \\\@array for Mouse";
292             }
293             else
294             {
295 0           local %_ = %spec;
296 0           local $_ = $attr;
297 0           $coderef->($attr, %_);
298 0           @_ = ($attr, %_);
299 0           goto $orig;
300             }
301 0           };
302             }
303              
304             1;
305              
306             __END__
307              
308             =pod
309              
310             =encoding utf-8
311              
312             =for stopwords metathingies munges mungers
313              
314             =begin private
315              
316             =item eval_closure
317              
318             =end private
319              
320             =head1 NAME
321              
322             MooseX::MungeHas - munge your "has" (works with Moo, Moose and Mouse)
323              
324             =head1 SYNOPSIS
325              
326             package Foo::Bar;
327            
328             use Moose;
329             use MooseX::MungeHas "is_ro";
330            
331             has foo => (); # read-only
332             has bar => (is => "rw"); # read-write
333              
334             =head1 DESCRIPTION
335              
336             MooseX::MungeHas alters the behaviour of the attributes of your L<Moo>,
337             L<Moose> or L<Mouse> based class. It manages to support all three because
338             it doesn't attempt to do anything smart with metathingies; it simply
339             installs a wrapper for C<< has >> that munges the attribute specification
340             hash before passing it on to the original C<< has >> function.
341              
342             The following munges are always applied (simply because I can see no
343             sensible reason why you would not want them to be).
344              
345             =over
346              
347             =item *
348              
349             Implement C<< is => "rwp" >> and C<< is => "lazy" >> in L<Moose> and
350             L<Mouse>.
351              
352             (These already work in L<Moo>.)
353              
354             =item *
355              
356             Implement C<< builder => 1 >>, C<< clearer => 1 >>, C<< predicate => 1 >>,
357             and C<< trigger => 1 >> in L<Moose> and L<Mouse>.
358              
359             (These already work in L<Moo>.)
360              
361             =item *
362              
363             Implement C<< builder => sub { ... } >> in L<Moose> and L<Mouse>.
364              
365             (This already works in L<Moo>.)
366              
367             =item *
368              
369             Allow L<Moo> to support C<< coerce => 0|1 >> for L<Type::Tiny> type
370             constraints. (Moo normally expects a coderef for the coercion.)
371              
372             (These already work in L<Moose> and L<Mouse>, and has actually been
373             added to L<Moo> as of version 1.006000.)
374              
375             =item *
376              
377             Makes C<< has $name => sub { ... } >> into a shortcut for:
378              
379             has $name => (is => "lazy", builder => sub { ... });
380              
381             =item *
382              
383             Makes C<< has $name => $type_constraint >> into a shortcut for:
384              
385             has $name => (isa => $type_constraint);
386              
387             (Assuming that C<< $type_constraint >> is a blessed type constraint
388             object a la L<Type::Tiny>, L<MooseX::Types>, etc.)
389              
390             =back
391              
392             When you import this module (i.e. C<< use MooseX::MungeHas >>) you can
393             provide a list of additional mungers you want it to apply. These may be
394             provided as coderefs, though for a few common, useful sets of behaviour,
395             there are pre-defined shortcut strings.
396              
397             # "no_isa" is a pre-defined shortcut;
398             # the other munger is a coderef.
399             #
400             use MooseX::MungeHas "no_isa", sub {
401             # Make constructor ignore private attributes
402             $_{init_arg} = undef if /^_/;
403             };
404              
405             Within coderefs, the name of the attribute being processed is available
406             in the C<< $_ >> variable, and the specification hash is available as
407             C<< %_ >>.
408              
409             You may provide multiple coderefs.
410              
411             The following are the pre-defined shortcuts:
412              
413             =over
414              
415             =item C<< is_ro >>, C<< is_rw >>, C<< is_rwp >>, C<< is_lazy >>
416              
417             These mungers supply defaults for the C<< is >> option.
418              
419             =item C<< always_coerce >>
420              
421             Automatically provides C<< coerce => 1 >> if the type constraint provides
422             coercions. (Unless you've explicitly specified C<< coerce => 0 >>.)
423              
424             =item C<< no_isa >>
425              
426             Switches off C<< isa >> checks for attributes, unless they coerce.
427              
428             =item C<< simple_isa >>
429              
430             Loosens type constraints if they don't coerce, and if it's likely to make
431             them significantly faster. (Loosening C<Int> to C<Num> won't speed it
432             up.)
433              
434             Only works if you're using L<Type::Tiny> constraints.
435              
436             =back
437              
438             Mungers provided as coderefs are executed I<after> predefined ones, but
439             are otherwise executed in the order specified.
440              
441             =head2 Multiple Wrappers
442              
443             Since version 0.007, it has been possible to use MooseX::MungeHas to
444             export multiple wrappers with different names:
445              
446             package Foo;
447             use Moose;
448             use MooseX::MungeHas {
449             has_ro => [ "is_ro", "always_coerce" ],
450             has_rw => [ "is_rw", "always_coerce" ],
451             };
452            
453             has_ro "foo" => (required => 1);
454             has_rw "bar";
455              
456             Note in the example above, MooseX::MungeHas has installed two brand new
457             wrapped C<has> functions with different names, but it has left the
458             standard C<has> function unmolested.
459              
460             If you wanted to alter the standard function too, then you could use:
461              
462             package Foo;
463             use Moose;
464             use MooseX::MungeHas {
465             has => [ "always_coerce" ],
466             has_ro => [ "is_ro", "always_coerce" ],
467             has_rw => [ "is_rw", "always_coerce" ],
468             };
469            
470             has_ro "foo" => (required => 1);
471             has_rw "bar";
472              
473             =head1 BUGS
474              
475             Please report any bugs to
476             L<http://rt.cpan.org/Dist/Display.html?Queue=MooseX-MungeHas>.
477              
478             =head1 SEE ALSO
479              
480             L<Moo>, L<Mouse>, L<Moose>, L<MooseX::AttributeShortcuts>,
481             L<MooseX::InlineTypes>, L<Type::Tiny::Manual>.
482              
483             Similar: L<MooseX::HasDefaults>, L<MooseX::Attributes::Curried>,
484             L<MooseX::Attribute::Prototype> and L<MooseX::AttributeDefaults>.
485              
486             =head1 AUTHOR
487              
488             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
489              
490             =head1 COPYRIGHT AND LICENCE
491              
492             This software is copyright (c) 2013-2014, 2017 by Toby Inkster.
493              
494             This is free software; you can redistribute it and/or modify it under
495             the same terms as the Perl 5 programming language system itself.
496              
497             =head1 DISCLAIMER OF WARRANTIES
498              
499             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
500             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
501             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
502