File Coverage

blib/lib/Moxie/Traits/Provider.pm
Criterion Covered Total %
statement 218 237 91.9
branch 58 86 67.4
condition 17 39 43.5
subroutine 44 45 97.7
pod 0 9 0.0
total 337 416 81.0


line stmt bran cond sub pod time code
1             package Moxie::Traits::Provider;
2             # ABSTRACT: built in traits
3              
4 49     69   528 use v5.22;
  49         158  
5 49     49   275 use warnings;
  49         91  
  49         1781  
6 49         266 use experimental qw[
7             signatures
8             postderef
9 49     49   269 ];
  49         95  
10              
11 49     49   6658 use Method::Traits ':for_providers';
  49         104  
  49         409  
12              
13 49     49   18507 use Carp ();
  49         107  
  49         944  
14 49     49   12392 use Sub::Util (); # for setting the prototype of the lexical accessors
  49         12949  
  49         1157  
15 49     49   12841 use PadWalker (); # for generating lexical accessors
  49         25946  
  49         1325  
16 49     49   301 use MOP::Util ();
  49         94  
  49         43007  
17              
18             our $VERSION = '0.06';
19             our $AUTHORITY = 'cpan:STEVAN';
20              
21 24     24 0 50 sub init ( $meta, $method, %init_args ) : OverwritesMethod {
  24         10931  
  24         71  
  24         47  
  24         72  
22              
23             # XXX:
24             # Consider perhaps supporting something
25             # like the Perl 6 signature format here,
26             # which would give us a more sophisticated
27             # way to specify the constructor API
28             #
29             # The way MAIN is handled is good inspiration maybe ...
30             # http://perl6maven.com/parsing-command-line-arguments-perl6
31             #
32             # - SL
33              
34 24         88 my $class_name = $meta->name;
35 24         386 my $method_name = $method->name;
36              
37 24 50       382 Carp::confess('The `init_arg` trait can only be applied to BUILDARGS')
38             if $method_name ne 'BUILDARGS';
39              
40 24 100       75 if ( %init_args ) {
41              
42 23         125 my @all = sort keys %init_args;
43 23         137 my @required = grep !/\?$/, @all;
44              
45 23         63 my $max_arity = 2 * scalar @all;
46 23         378 my $min_arity = 2 * scalar @required;
47              
48             # use Data::Dumper;
49             # warn Dumper {
50             # class => $meta->name,
51             # all => \@all,
52             # required => \@required,
53             # min_arity => $min_arity,
54             # max_arity => $max_arity,
55             # };
56              
57 77     77   131 $meta->add_method('BUILDARGS' => sub ($self, @args) {
  5     411   5999  
  5     171   8  
  5         11  
  77         69455  
  77         168  
  77         164  
58              
59 77         138 my $arity = scalar @args;
60              
61 77 100 100     3027 Carp::confess('Constructor for ('.$class_name.') expected '
    100          
62             . (($max_arity == $min_arity)
63             ? ($min_arity)
64             : ('between '.$min_arity.' and '.$max_arity))
65             . ' arguments, got ('.$arity.')')
66             if $arity < $min_arity || $arity > $max_arity;
67              
68 67         260 my $proto = $self->UNIVERSAL::Object::BUILDARGS( @args );
69              
70 67         764 my @missing;
71             # make sure all the expected parameters exist ...
72 67         161 foreach my $param ( @required ) {
73 23 100       77 push @missing => $param unless exists $proto->{ $param };
74             }
75              
76 67 100       1642 Carp::confess('Constructor for ('.$class_name.') missing (`'.(join '`, `' => @missing).'`) parameters, got (`'.(join '`, `' => sort keys $proto->%*).'`), expected (`'.(join '`, `' => @all).'`)')
77             if @missing;
78              
79 60         134 my (%final, %super);
80              
81             #warn "---------------------------------------";
82             #warn join ', ' => @all;
83              
84             # do any kind of slot assignment shuffling needed ....
85 60         129 foreach my $param ( @all ) {
86              
87             #warn "CHECKING param: $param";
88              
89 92         199 my $from = $param;
90 92         394 $from =~ s/\?$//;
91 92         220 my $to = $init_args{ $param };
92              
93             #warn "PARAM: $param FROM: ($from) TO: ($to)";
94              
95 92 100       263 if ( $to =~ /^super\((.*)\)$/ ) {
96             $super{ $1 } = delete $proto->{ $from }
97 10 100       41 if $proto->{ $from };
98             }
99             else {
100 82 100       228 if ( exists $proto->{ $from } ) {
101              
102             #use Data::Dumper;
103             #warn "BEFORE:", Dumper $proto;
104              
105             # now grab the slot by the correct name ...
106 46         162 $final{ $to } = delete $proto->{ $from };
107              
108             #warn "AFTER:", Dumper $proto;
109             }
110             #else {
111             #use Data::Dumper;
112             #warn "NOT FOUND ($from) :", Dumper $proto;
113             #}
114             }
115             }
116              
117             # inherit keys ...
118 60 100       192 if ( keys %super ) {
119 4         22 my $super_proto = $self->next::method( %super );
120 4         21 %final = ( $super_proto->%*, %final );
121             }
122              
123 60 100       199 if ( keys $proto->%* ) {
124              
125             #use Data::Dumper;
126             #warn Dumper +{
127             # proto => $proto,
128             # final => \%final,
129             # super => \%super,
130             # meta => {
131             # class => $meta->name,
132             # all => \@all,
133             # required => \@required,
134             # min_arity => $min_arity,
135             # max_arity => $max_arity,
136             # }
137             #};
138              
139 2         500 Carp::confess('Constructor for ('.$class_name.') got unrecognized parameters (`'.(join '`, `' => keys $proto->%*).'`)');
140             }
141              
142 58         234 return \%final;
143 23         279 });
144             }
145             else {
146 5     10   6 $meta->add_method('BUILDARGS' => sub ($self, @args) {
  0         0  
  0         0  
  0         0  
147 5 100       966 Carp::confess('Constructor for ('.$class_name.') expected 0 arguments, got ('.(scalar @args).')')
148             if @args;
149 1         5 return $self->UNIVERSAL::Object::BUILDARGS();
150 1         14 });
151             }
152 49     49   369 }
  49         103  
  49         397  
153              
154 37     37 0 70 sub ro ( $meta, $method, @args ) : OverwritesMethod {
  37         21196  
  37         75  
  37         67  
  37         95  
155              
156 37         151 my $method_name = $method->name;
157              
158 37         664 my $slot_name;
159 37 100       121 if ( $args[0] ) {
160 32         74 $slot_name = shift @args;
161             }
162             else {
163 5 100       20 if ( $method_name =~ /^get_(.*)$/ ) {
164 1         39 $slot_name = $1;
165             }
166             else {
167 4         10 $slot_name = $method_name;
168             }
169             }
170              
171 37 50 33     162 Carp::confess('Unable to build `ro` accessor for slot `' . $slot_name.'` in `'.$meta->name.'` because the slot cannot be found.')
172             unless $meta->has_slot( $slot_name )
173             || $meta->has_slot_alias( $slot_name );
174              
175             $meta->add_method( $method_name => sub {
176 205 50   205   27573 Carp::confess("Cannot assign to `$slot_name`, it is a readonly") if scalar @_ != 1;
        33      
        33      
        33      
        52      
177 205         840 $_[0]->{ $slot_name };
178 37         6001 });
179 49     49   44496 }
  49         106  
  49         192  
180              
181 1     1 0 2 sub rw ( $meta, $method, @args ) : OverwritesMethod {
  1         677  
  1         2  
  1         2  
  1         1  
182              
183 1         3 my $method_name = $method->name;
184              
185 1         13 my $slot_name;
186 1 50       3 if ( $args[0] ) {
187 0         0 $slot_name = shift @args;
188             }
189             else {
190 1         2 $slot_name = $method_name;
191             }
192              
193 1 50       3 Carp::confess('Unable to build `rw` accessor for slot `' . $slot_name.'` in `'.$meta->name.'` because class is immutable.')
194             if ($meta->name)->isa('Moxie::Object::Immutable');
195              
196 1 50 33     20 Carp::confess('Unable to build `rw` accessor for slot `' . $slot_name.'` in `'.$meta->name.'` because the slot cannot be found.')
197             unless $meta->has_slot( $slot_name )
198             || $meta->has_slot_alias( $slot_name );
199              
200             $meta->add_method( $method_name => sub {
201 0 0   0   0 $_[0]->{ $slot_name } = $_[1] if scalar( @_ ) > 1;
202 0         0 $_[0]->{ $slot_name };
203 1         124 });
204 49     49   32268 }
  49         108  
  49         197  
205              
206 14     14 0 25 sub wo ( $meta, $method, @args ) : OverwritesMethod {
  14         9293  
  14         30  
  14         22  
  14         118  
207              
208 14         57 my $method_name = $method->name;
209              
210 14         224 my $slot_name;
211 14 100       51 if ( $args[0] ) {
212 13         34 $slot_name = shift @args;
213             }
214             else {
215 1 50       5 if ( $method_name =~ /^set_(.*)$/ ) {
216 1         3 $slot_name = $1;
217             }
218             else {
219 0         0 $slot_name = $method_name;
220             }
221             }
222              
223 14 50       50 Carp::confess('Unable to build `wo` accessor for slot `' . $slot_name.'` in `'.$meta->name.'` because class is immutable.')
224             if ($meta->name)->isa('Moxie::Object::Immutable');
225              
226 14 50 33     314 Carp::confess('Unable to build `wo` accessor for slot `' . $slot_name.'` in `'.$meta->name.'` because the slot cannot be found.')
227             unless $meta->has_slot( $slot_name )
228             || $meta->has_slot_alias( $slot_name );
229              
230             $meta->add_method( $method_name => sub {
231 51 50   51   269 Carp::confess("You must supply a value to write to `$slot_name`") if scalar(@_) < 1;
        82      
        82      
        52      
232 51         121 $_[0]->{ $slot_name } = $_[1];
233 14         2343 });
234 49     49   35704 }
  49         115  
  49         235  
235              
236 14     50 0 27 sub predicate ( $meta, $method, @args ) : OverwritesMethod {
  14         10152  
  14         35  
  14         26  
  14         37  
237              
238 14         69 my $method_name = $method->name;
239              
240 14         237 my $slot_name;
241 14 100       51 if ( $args[0] ) {
242 12         30 $slot_name = shift @args;
243             }
244             else {
245 2 50       12 if ( $method_name =~ /^has_(.*)$/ ) {
246 2         7 $slot_name = $1;
247             }
248             else {
249 0         0 $slot_name = $method_name;
250             }
251             }
252              
253 14 50 33     60 Carp::confess('Unable to build predicate for slot `' . $slot_name.'` in `'.$meta->name.'` because the slot cannot be found.')
254             unless $meta->has_slot( $slot_name )
255             || $meta->has_slot_alias( $slot_name );
256              
257 14     103   2238 $meta->add_method( $method_name => sub { defined $_[0]->{ $slot_name } } );
  67         32014  
258 49     49   33593 }
  49         116  
  49         200  
259              
260 10     109 0 17 sub clearer ( $meta, $method, @args ) : OverwritesMethod {
  10         7072  
  10         25  
  10         18  
  10         28  
261              
262 10         40 my $method_name = $method->name;
263              
264 10         177 my $slot_name;
265 10 50       49 if ( $args[0] ) {
266 10         40 $slot_name = shift @args;
267             }
268             else {
269 0 0       0 if ( $method_name =~ /^clear_(.*)$/ ) {
270 0         0 $slot_name = $1;
271             }
272             else {
273 0         0 $slot_name = $method_name;
274             }
275             }
276              
277 10 50       45 Carp::confess('Unable to build `clearer` accessor for slot `' . $slot_name.'` in `'.$meta->name.'` because class is immutable.')
278             if ($meta->name)->isa('Moxie::Object::Immutable');
279              
280 10 50 33     209 Carp::confess('Unable to build `clearer` accessor for slot `' . $slot_name.'` in `'.$meta->name.'` because the slot cannot be found.')
281             unless $meta->has_slot( $slot_name )
282             || $meta->has_slot_alias( $slot_name );
283              
284 10     36   1598 $meta->add_method( $method_name => sub { undef $_[0]->{ $slot_name } } );
  18         70  
285 49     49   34472 }
  49         111  
  49         249  
286              
287 3     33 0 6 sub lazy ( $meta, $method, @args ) : OverwritesMethod {
  3         2076  
  3         6  
  3         9  
  3         7  
288              
289 3         11 my $method_name = $method->name;
290              
291 3         50 my $slot_name;
292 3 100       9 if ( $args[0] ) {
293 1         3 $slot_name = shift @args;
294             }
295             else {
296 2 50       6 if ( $method_name =~ /^build_(.*)$/ ) {
297 0         0 $slot_name = $1;
298             }
299             else {
300 2         4 $slot_name = $method_name;
301             }
302             }
303              
304 3 50       16 Carp::confess('Unable to build `lazy` accessor for slot `' . $slot_name.'` in `'.$meta->name.'` because class is immutable.')
305             if ($meta->name)->isa('Moxie::Object::Immutable');
306              
307 3 50 33     63 Carp::confess('Unable to build `lazy` accessor for slot `' . $slot_name.'` in `'.$meta->name.'` because the slot cannot be found.')
308             unless $meta->has_slot( $slot_name )
309             || $meta->has_slot_alias( $slot_name );
310              
311              
312             # NOTE:
313             # lazy is read-only by design, if you want
314             # a rw+lazy, write it yourself
315             # - SL
316              
317 3         616 my $orig = $meta->get_method( $method_name )->body;
318              
319             $meta->add_method( $method_name => sub {
320 17   100 19   2097 $_[0]->{ $slot_name } //= $orig->( @_ );
321 3         431 });
322 49     49   34833 }
  49         109  
  49         202  
323              
324 2     19 0 4 sub handles ( $meta, $method, @args ) : OverwritesMethod {
  2         1270  
  2         5  
  2         3  
  2         4  
325              
326 2         5 my $method_name = $method->name;
327              
328 2         36 my ($slot_name, $delegate) = ($args[0] =~ /^(.*)\-\>(.*)$/);
329              
330 2 50 33     15 Carp::confess('Delegation spec must be in the pattern `slot->method`, not '.$args[0])
331             unless $slot_name && $delegate;
332              
333 2 50 33     6 Carp::confess('Unable to build delegation method for slot `' . $slot_name.'` in `'.$meta->name.'` because the slot cannot be found.')
334             unless $meta->has_slot( $slot_name )
335             || $meta->has_slot_alias( $slot_name );
336              
337             $meta->add_method( $method_name => sub {
338 4     16   1081 $_[0]->{ $slot_name }->$delegate( @_[ 1 .. $#_ ] );
339 2         299 });
340 49     49   31337 }
  49         117  
  49         211  
341              
342 36     40 0 9458 sub private ( $meta, $method, @args ) {
  36         83  
  36         61  
  36         65  
  36         57  
343              
344 36         147 my $method_name = $method->name;
345              
346 36         819 my $slot_name;
347 36 50       138 if ( $args[0] ) {
348 0         0 $slot_name = shift @args;
349             }
350             else {
351 36         91 $slot_name = $method_name;
352             }
353              
354 36 50 33     164 Carp::confess('Unable to build private accessor for slot `' . $slot_name.'` in `'.$meta->name.'` because the slot cannot be found.')
355             unless $meta->has_slot( $slot_name )
356             || $meta->has_slot_alias( $slot_name );
357              
358             # NOTE:
359             # These are lexical accessors ...
360              
361             # we should not be able to find it in the symbol table ...
362 36 50 33     5945 if ( $meta->has_method( $method_name ) || $meta->has_method_alias( $method_name ) || $meta->requires_method( $method_name ) ) {
      33        
363 0         0 Carp::confess('Unable to install private (lexical) accessor for slot('.$slot_name.') named ('
364             .$method_name.') because we found a conflicting non-lexical method of that name. '
365             .'Private methods must be defined before any public methods of the same name.');
366             }
367             else {
368             # set the prototype here so that the compiler sees
369             # this as early as possible ...
370 36         2577 Sub::Util::set_prototype( '', $method->body );
371              
372             # at this point we can assume that we have a lexical
373             # method which we need to transform, and in order to
374             # do that we need to look at all the methods in this
375             # class and find all the ones who 'close over' the
376             # lexical method and then re-write their lexical pad
377             # to use the accessor method that I will generate.
378              
379             # NOTE:
380             # we need to delay this until the UNITCHECK phase
381             # because we need all the methods of this class to
382             # have been compiled, at this moment, they are not.
383             MOP::Util::defer_until_UNITCHECK(sub {
384              
385             # now see if this class is immutable or not, it will
386             # determine the type of accessor we generate ...
387 36     36   1049 my $class_is_immutable = ($meta->name)->isa('Moxie::Object::Immutable');
388              
389             # now check the class local methods ....
390 36         724 foreach my $m ( $meta->methods ) {
391             # get a HASH of the things the method closes over
392 160         27956 my $closed_over = PadWalker::closed_over( $m->body );
393              
394             #warn Data::Dumper::Dumper({
395             # class => $meta->name,
396             # method => $m->name,
397             # closed_over => $closed_over,
398             # looking_for => $method_name,
399             #});
400              
401             # XXX:
402             # Consider using something like Text::Levenshtein
403             # to check for typos in the accessor usage.
404             # - SL
405              
406             # if the private method is used, then it will be
407             # here with a prepended `&` sigil ...
408 160 100       1576 if ( exists $closed_over->{ '&' . $method_name } ) {
409             # now we know that we have someone using the
410             # lexical method inside the method body, so
411             # we need to generate our accessor accordingly
412              
413             # XXX:
414             # The DB::args stuff below is fragile because it
415             # is susceptible to alteration of @_ in the
416             # method that calls these accessors. Perhaps this
417             # can be fixed with XS, but for now we are going
418             # to assume people aren't doing this since they
419             # *should* be using the signatures that we enable
420             # for them.
421             # - SL
422              
423 62         162 my $accessor;
424 62 50       156 if ( $class_is_immutable ) {
425             # NOTE:
426             # if the class is immutable, perl will sometimes
427             # complain about accessing a read-only value in
428             # a way it is not comfortable, and this can be
429             # annoying. However, since we actually told perl
430             # that we want to be immutable, there actually is
431             # no need to generate the lvalue accessor when
432             # we can make a read-only one.
433             # - SL
434             $accessor = sub {
435 0         0 package DB; @DB::args = (); my () = caller(1);
  0         0  
436 0         0 my ($self) = @DB::args;
437 0         0 $self->{ $slot_name };
438 0         0 };
439             }
440             else {
441             $accessor = sub : lvalue {
442 250     52   39760 package DB; @DB::args = (); my () = caller(1);
  250         1396  
443 250         544 my ($self) = @DB::args;
444 250         876 $self->{ $slot_name };
445 62         273 };
446             }
447              
448             # then this is as simple as assigning the HASH key
449 62         162 $closed_over->{ '&' . $method_name } = $accessor;
450              
451             # okay, now restore the closed over vars
452             # with our new addition...
453 62         157 PadWalker::set_closed_over( $m->body, $closed_over );
454             }
455             }
456 36         469 });
457             }
458              
459             }
460              
461             1;
462              
463             __END__