File Coverage

blib/lib/MooseX/AttributeShortcuts/Trait/Attribute.pm
Criterion Covered Total %
statement 136 144 94.4
branch 57 74 77.0
condition 9 12 75.0
subroutine 31 32 96.8
pod 1 1 100.0
total 234 263 88.9


line stmt bran cond sub pod time code
1             #
2             # This file is part of MooseX-AttributeShortcuts
3             #
4             # This software is Copyright (c) 2017, 2015, 2014, 2013, 2012, 2011 by Chris Weyl.
5             #
6             # This is free software, licensed under:
7             #
8             # The GNU Lesser General Public License, Version 2.1, February 1999
9             #
10             package MooseX::AttributeShortcuts::Trait::Attribute;
11             our $AUTHORITY = 'cpan:RSRCHBOY';
12             $MooseX::AttributeShortcuts::Trait::Attribute::VERSION = '0.037';
13             # ABSTRACT: Shortcuts attribute trait proper
14              
15 28     28   176 use namespace::autoclean;
  28         59  
  28         161  
16 28     28   9740 use MooseX::Role::Parameterized;
  28         2095707  
  28         136  
17 28     28   985498 use Moose::Util::TypeConstraints ':all';
  28         81  
  28         295  
18 28     28   68939 use MooseX::Types::Moose ':all';
  28         1377997  
  28         353  
19 28     28   249739 use MooseX::Types::Common::String ':all';
  28         2595386  
  28         239  
20 28     28   140912 use MooseX::Util;
  28         1252276  
  28         273  
21              
22 28     28   15236 use aliased 'MooseX::Meta::TypeConstraint::Mooish' => 'MooishTC';
  28         16030  
  28         220  
23 28     28   795299 use aliased 'MooseX::AttributeShortcuts::Trait::Method::Builder' => 'BuilderTrait';
  28         75  
  28         173  
24              
25 28     28   3040 use List::Util 1.33 'any';
  28         747  
  28         52625  
26              
27             # lazy...
28             my $_acquire_isa_tc = sub { goto \&Moose::Util::TypeConstraints::find_or_create_isa_type_constraint };
29              
30              
31             parameter writer_prefix => (isa => NonEmptySimpleStr, default => '_set_');
32             parameter builder_prefix => (isa => NonEmptySimpleStr, default => '_build_');
33              
34             with 'MooseX::AttributeShortcuts::Trait::Attribute::HasAnonBuilder';
35              
36              
37             has constraint => (
38             is => 'ro',
39             isa => 'CodeRef',
40             predicate => 'has_constraint',
41             );
42              
43             has original_isa => (
44             is => 'ro',
45             predicate => 'has_original_isa',
46             );
47              
48             has trigger_method => (
49             is => 'ro',
50             predicate => 'has_trigger_method',
51             );
52              
53              
54             after attach_to_class => sub {
55             my ($self, $class) = @_;
56              
57             return unless $self->has_anon_builder && !$self->anon_builder_installed;
58              
59             ### install our anon builder as a method: $class->name
60             # $class->add_method($self->builder => $self->anon_builder);
61             $class->add_method($self->builder => $self->_builder_method_meta($class));
62             $self->_set_anon_builder_installed;
63              
64             return;
65             };
66              
67              
68             before _process_options => sub { shift->_mxas_process_options(@_) };
69              
70             # this feels... bad. But I'm not sure there's any way to ensure we
71             # process options on a clone/extends without wrapping new().
72              
73             around new => sub {
74             my ($orig, $self) = (shift, shift);
75             my ($name, %options) = @_;
76              
77             $self->_mxas_process_options($name, \%options)
78             if $options{__hack_no_process_options};
79              
80             return $self->$orig($name, %options);
81             };
82              
83              
84             # NOTE: remove_delegation() will also automagically remove any custom
85             # accessors we create here
86              
87             # handle: handles => { name => sub { ... }, ... }
88             around _make_delegation_method => sub {
89             my ($orig, $self) = (shift, shift);
90             my ($name, $coderef) = @_;
91              
92             ### _make_delegation_method() called with a: ref $coderef
93             return $self->$orig(@_)
94             unless 'CODE' eq ref $coderef;
95              
96             # this coderef will be installed as a method on the associated class itself.
97             my $custom_coderef = sub {
98             # aka $self from the class instance's perspective
99 2         7570 my $associated_class_instance = shift @_;
100              
101             # in $coderef, $_ will be the attribute metaclass
102 2         6 local $_ = $self;
103 2         7 return $associated_class_instance->$coderef(@_);
104             };
105              
106             return $self->_process_accessors(custom => { $name => $custom_coderef });
107             };
108              
109             sub _mxas_process_options {
110 64     64   177 my ($class, $name, $options) = @_;
111              
112 64     713   318 my $_has = sub { defined $options->{$_[0]} };
  713         2920  
113 64 100   317   256 my $_opt = sub { $_has->(@_) ? $options->{$_[0]} : q{} };
  317         545  
114 64 100   158   223 my $_ref = sub { ref $_opt->(@_) || q{} };
  158         297  
115              
116             # handle: is => ...
117 64         338 $class->_mxas_is_rwp($name, $options, $_has, $_opt, $_ref);
118 64         283 $class->_mxas_is_lazy($name, $options, $_has, $_opt, $_ref);
119              
120             # handle: init_arg => 1/-1
121 64         261 $class->_mxas_init_arg($name, $options, $_has, $_opt, $_ref);
122              
123             # handle: builder => 1, builder => sub { ... }
124 64         260 $class->_mxas_builder($name, $options, $_has, $_opt, $_ref);
125              
126             # handle: isa_instance_of => ...
127 64         278 $class->_mxas_isa_instance_of($name, $options, $_has, $_opt, $_ref);
128             # handle: isa => sub { ... }
129 64         263 $class->_mxas_isa_mooish($name, $options, $_has, $_opt, $_ref);
130              
131             # handle: constraint => ...
132 64         272 $class->_mxas_constraint($name, $options, $_has, $_opt, $_ref);
133             # handle: coerce => [ ... ]
134 64         262 $class->_mxas_coerce($name, $options, $_has, $_opt, $_ref);
135              
136              
137 64         292 my %prefix = (
138             predicate => 'has',
139             clearer => 'clear',
140             trigger => '_trigger_',
141             );
142              
143 64 100   32   255 my $is_private = sub { $name =~ /^_/ ? $_[0] : $_[1] };
  32         107  
144             my $default_for = sub {
145 128     128   265 my ($opt) = @_;
146              
147 128 100       237 return unless $_has->($opt);
148 16         31 my $opt_val = $_opt->($opt);
149              
150 16 50       62 my ($head, $mid)
    100          
151             = $opt_val eq '1' ? ($is_private->('_', q{}), $is_private->(q{}, '_'))
152             : $opt_val eq '-1' ? ($is_private->(q{}, '_'), $is_private->(q{}, '_'))
153             : return;
154              
155 16         57 $options->{$opt} = $head . $prefix{$opt} . $mid . $name;
156 16         40 return;
157 64         316 };
158              
159             ### set our other defaults, if requested...
160 64         243 $default_for->($_) for qw{ predicate clearer };
161 64         202 my $trigger = "$prefix{trigger}$name";
162 5     10   19 do { $options->{trigger} = sub { shift->$trigger(@_) }; $options->{trigger_method} = $trigger }
  10         55013  
  5         14  
163 64 100 66     261 if $options->{trigger} && $options->{trigger} eq '1';
164              
165 64         778 return;
166             }
167              
168             # The following two methods are here both to help ensure compatibility with
169             # MooseX::SemiAffordanceAccessor and to enable other packages to modify our
170             # behaviour.
171              
172             sub _mxas_writer_name {
173 14     14   40 my ($class, $name) = @_;
174              
175 14 50       72 return $class->canonical_writer_prefix . $name
176             unless $class->meta->does_role('MooseX::SemiAffordanceAccessor::Role::Attribute');
177              
178             # ok, if we're here then we need to follow that role's scheme
179 0 0       0 return $name =~ /^_/ ? "_set$name" : "set_$name";
180             };
181              
182             sub _mxas_private_writer_name {
183 14     14   43 my ($class, $name) = @_;
184              
185 14         53 $name = $class->_mxas_writer_name($name);
186 14 50       244 return $name =~ /^_/ ? $name : "_$name";
187             }
188              
189             # handle: is => 'rwp'
190             sub _mxas_is_rwp {
191 64     64   188 my ($class, $name, $options, $_has, $_opt, $_ref) = @_;
192              
193 64 100       168 return unless $_opt->('is') eq 'rwp';
194              
195 14         42 $options->{is} = 'ro';
196 14         54 $options->{writer} = $class->_mxas_private_writer_name($name);
197              
198 14         32 return;
199             }
200              
201             # handle: init_arg => 1/-1
202             sub _mxas_init_arg {
203 64     64   187 my ($class, $name, $options, $_has, $_opt, $_ref) = @_;
204              
205 64 100       200 return unless exists $options->{init_arg};
206              
207 5 100       18 my $one = ($name !~ /^_/) ? $name : $name;
208 5 100       17 my $not_one = ($name !~ /^_/) ? "_$name" : do { (local $_ = $name) =~ s/^_//; $_ };
  2         8  
  2         4  
209              
210             $options->{init_arg}
211             = ! defined $options->{init_arg} ? return
212             : "$options->{init_arg}" eq 1 ? $one
213             : "$options->{init_arg}" eq -1 ? $not_one
214             : $options->{init_arg}
215 5 100       31 ;
    100          
    50          
216              
217 5         10 return;
218             }
219              
220             # handle: is => 'lazy'
221             sub _mxas_is_lazy {
222 64     64   187 my ($class, $name, $options, $_has, $_opt, $_ref) = @_;
223              
224 64 100       145 return unless $_opt->('is') eq 'lazy';
225              
226 5         11 $options->{is} = 'ro';
227 5         11 $options->{lazy} = 1;
228 5 100 100     11 $options->{builder} = 1
229             unless $_has->('builder') || $_has->('default');
230              
231 5         11 return;
232             }
233              
234             # handle: lazy_build => 'private'
235             sub _mxas_lazy_build_private {
236 0     0   0 my ($class, $name, $options, $_has, $_opt, $_ref) = @_;
237              
238 0 0       0 return unless $_opt->('lazy_build') eq 'private';
239              
240 0         0 $options->{lazy_build} = 1;
241 0         0 $options->{clearer} = "_clear_$name";
242 0         0 $options->{predicate} = "_has_$name";
243              
244 0         0 return;
245             }
246              
247             # handle: builder => 1, builder => sub { ... }
248             sub _mxas_builder {
249 64     64   176 my ($class, $name, $options, $_has, $_opt, $_ref) = @_;
250              
251 64 100       139 return unless $_has->('builder');
252              
253 24 100       80 if ($_ref->('builder') eq 'CODE') {
254              
255 3         8 $options->{anon_builder} = $options->{builder};
256 3         6 $options->{builder} = 1;
257             }
258              
259             $options->{builder} = $class->_mxas_builder_name($name)
260 24 100       178 if $options->{builder} eq '1';
261              
262 24         251 return;
263             }
264              
265             sub _mxas_isa_mooish {
266 64     64   173 my ($class, $name, $options, $_has, $_opt, $_ref) = @_;
267              
268 64 100       150 return unless $_ref->('isa') eq 'CODE';
269              
270             ### build a mooish type constraint...
271 1         2 $options->{original_isa} = $options->{isa};
272 1         16 $options->{isa} = MooishTC->new(constraint => $options->{isa});
273              
274 1         1691 return;
275             }
276              
277             # handle: isa_instance_of => ...
278             sub _mxas_isa_instance_of {
279 64     64   180 my ($class, $name, $options, $_has, $_opt, $_ref) = @_;
280              
281 64 100       147 return unless $_has->('isa_instance_of');
282              
283 1 50       3 if ($_has->('isa')) {
284              
285             $class->throw_error(
286             q{Cannot use 'isa_instance_of' and 'isa' together for attribute }
287 0         0 . $_opt->('definition_context')->{package} . '::' . $name
288             );
289             }
290              
291 1         7 $options->{isa} = class_type(delete $options->{isa_instance_of});
292              
293 1         1810 return;
294             }
295              
296             # handle: coerce => [ ... ]
297             sub _mxas_coerce {
298 64     64   180 my ($class, $name, $options, $_has, $_opt, $_ref) = @_;
299              
300 64 100       136 if ($_ref->('coerce') eq 'ARRAY') {
301              
302             ### must be type => sub { ... } pairs...
303 1         2 my @coercions = @{ $_opt->('coerce') };
  1         3  
304 1 50       3 confess 'You must specify an "isa" when declaring "coercion"'
305             unless $_has->('isa');
306 1 50       5 confess 'coercion array must be in pairs!'
307             if @coercions % 2;
308 1 50       3 confess 'must define at least one coercion pair!'
309             unless @coercions > 0;
310              
311 1         11 my $our_coercion = Moose::Meta::TypeCoercion->new;
312             my $our_type
313             = $options->{original_isa}
314             ? $options->{isa}
315 1 50       424 : $_acquire_isa_tc->($_opt->('isa'))->create_child_type
316             ;
317              
318 1         2491 $our_coercion->add_type_coercions(@coercions);
319 1         436 $our_type->coercion($our_coercion);
320              
321 1   33     16 $options->{original_isa} ||= $options->{isa};
322 1         5 $options->{isa} = $our_type;
323 1         2 $options->{coerce} = 1;
324              
325 1         3 return;
326             }
327              
328             # If our original constraint has coercions and our created subtype
329             # did not have any (as specified in the 'coerce' option), then
330             # copy the parent's coercions over.
331              
332 63 100 100     168 if ($_has->('original_isa') && $_opt->('coerce') eq '1') {
333              
334 1         3 my $isa_type = $_acquire_isa_tc->($_opt->('original_isa'));
335              
336 1 50       78 if ($isa_type->has_coercion) {
337              
338             # create our coercion as a copy of the parent
339             $_opt->('isa')->coercion(Moose::Meta::TypeCoercion->new(
340             type_constraint => $_opt->('isa'),
341 1         303 type_coercion_map => [ @{ $isa_type->coercion->type_coercion_map } ],
  1         6  
342             ));
343             }
344              
345             }
346              
347 63         1813 return;
348             }
349              
350             sub _mxas_constraint {
351 64     64   186 my ($class, $name, $options, $_has, $_opt, $_ref) = @_;
352              
353 64 100       177 return unless $_has->('constraint');
354              
355             # check for errors...
356 3 50       9 $class->throw_error('You must specify an "isa" when declaring a "constraint"')
357             if !$_has->('isa');
358 3 50       9 $class->throw_error('"constraint" must be a CODE reference')
359             if $_ref->('constraint') ne 'CODE';
360              
361             # constraint checking! XXX message, etc?
362 3 50       9 push my @opts, constraint => $_opt->('constraint')
363             if $_ref->('constraint') eq 'CODE';
364              
365             # stash our original option away and construct our new one
366 3         8 my $isa = $options->{original_isa} = $_opt->('isa');
367 3         11 $options->{isa} = $_acquire_isa_tc->($isa)->create_child_type(@opts);
368              
369 3         3463 return;
370             }
371              
372              
373             sub builder_method_metaclass {
374 3     3 1 7 my $self = shift @_;
375              
376 3         30 return with_traits($self->associated_class->method_metaclass => BuilderTrait);
377             }
378              
379              
380             role {
381             my $p = shift @_;
382              
383 14     14   6392 method canonical_writer_prefix => sub { $p->writer_prefix };
        4      
384 18     18   110 method canonical_builder_prefix => sub { $p->builder_prefix };
        6      
385             };
386              
387             !!42;
388              
389             __END__
390              
391             =pod
392              
393             =encoding UTF-8
394              
395             =for :stopwords Chris Weyl Alders David Etheridge Graham Karen Knop Olaf Steinbrunner
396              
397             =head1 NAME
398              
399             MooseX::AttributeShortcuts::Trait::Attribute - Shortcuts attribute trait proper
400              
401             =head1 VERSION
402              
403             This document describes version 0.037 of MooseX::AttributeShortcuts::Trait::Attribute - released November 20, 2017 as part of MooseX-AttributeShortcuts.
404              
405             =head1 DESCRIPTION
406              
407             This is the actual attribute trait that implements
408             L<MooseX::AttributeShortcuts>. You should consult that package's
409             documentation for information on any of the new attribute options; we're
410             mainly going to document the additional attributes, methods, and role
411             parameters that this role provides.
412              
413             All methods we include that chain off Moose's C<_process_options()> are
414             prefixed with C<_mxas_> and generally are not documented in the POD; we
415             document any internal methods of L<Moose::Meta::Attribute> that we wrap or
416             otherwise override we document here as well.
417              
418             =head1 ROLE PARAMETERS
419              
420             Parameterized roles accept parameters that influence their construction. This role accepts the following parameters.
421              
422             =head2 writer_prefix
423              
424             =head2 builder_prefix
425              
426             =head1 ATTRIBUTES
427              
428             =head2 constraint
429              
430             CodeRef, read-only.
431              
432             =head2 original_isa
433              
434             =head2 trigger_method
435              
436             Contains the name of the method that will be invoked as a trigger.
437              
438             =head1 BEFORE METHOD MODIFIERS
439              
440             =head2 _process_options
441              
442             Here we wrap _process_options() instead of the newer _process_is_option(), as
443             that makes our life easier from a Moose 1.x/2.x compatibility perspective --
444             and that we're generally altering more than just the 'is' option at one time.
445              
446             =head1 AROUND METHOD MODIFIERS
447              
448             =head2 _make_delegation_method
449              
450             Here we create and install any custom accessors that have been defined.
451              
452             =head1 AFTER METHOD MODIFIERS
453              
454             =head2 attach_to_class
455              
456             We hijack attach_to_class in order to install our anon_builder, if we have
457             one. Note that we don't go the normal associate_method/install_accessor/etc
458             route as this is kinda... different. (That is, the builder is not an
459             accessor of this attribute, and should not be installed as such.)
460              
461             =head1 METHODS
462              
463             =head2 has_constraint
464              
465             Predicate for the L</constraint> attribute.
466              
467             =head2 has_original_isa
468              
469             Predicate for the L</original_isa> attribute.
470              
471             =head2 has_trigger_method
472              
473             Predicate for the L</trigger_method> attribute.
474              
475             =head2 builder_method_metaclass()
476              
477             Returns the metaclass we'll use to install a inline builder.
478              
479             =head2 canonical_writer_prefix
480              
481             Returns the writer prefix; this is almost always C<set_>.
482              
483             =head2 canonical_builder_prefix
484              
485             Returns the builder prefix; this is almost always C<_build_>.
486              
487             =head1 PREFIXES
488              
489             We accept two parameters on the use of this module; they impact how builders
490             and writers are named.
491              
492             =head2 -writer_prefix
493              
494             use MooseX::::AttributeShortcuts -writer_prefix => 'prefix';
495              
496             The default writer prefix is C<_set_>. If you'd prefer it to be something
497             else (say, C<_>), this is where you'd do that.
498              
499             =head2 -builder_prefix
500              
501             use MooseX::AttributeShortcuts -builder_prefix => 'prefix';
502              
503             The default builder prefix is C<_build_>, as this is what L<Moose/lazy_build>
504             does, and what people in general recognize as build methods.
505              
506             =head1 SEE ALSO
507              
508             Please see those modules/websites for more information related to this module.
509              
510             =over 4
511              
512             =item *
513              
514             L<MooseX::AttributeShortcuts|MooseX::AttributeShortcuts>
515              
516             =back
517              
518             =head1 BUGS
519              
520             Please report any bugs or feature requests on the bugtracker website
521             L<https://github.com/RsrchBoy/moosex-attributeshortcuts/issues>
522              
523             When submitting a bug or request, please include a test-file or a
524             patch to an existing test-file that illustrates the bug or desired
525             feature.
526              
527             =head1 AUTHOR
528              
529             Chris Weyl <cweyl@alumni.drew.edu>
530              
531             =head1 COPYRIGHT AND LICENSE
532              
533             This software is Copyright (c) 2017, 2015, 2014, 2013, 2012, 2011 by Chris Weyl.
534              
535             This is free software, licensed under:
536              
537             The GNU Lesser General Public License, Version 2.1, February 1999
538              
539             =cut