File Coverage

blib/lib/Class/Tiny/Antlers.pm
Criterion Covered Total %
statement 157 214 73.3
branch 78 124 62.9
condition 31 51 60.7
subroutine 32 36 88.8
pod 5 6 83.3
total 303 431 70.3


line stmt bran cond sub pod time code
1             package Class::Tiny::Antlers;
2              
3 2     2   7 sub _getstash { \%{"$_[0]::"} }
  2         8  
4              
5 11     11   663946 use 5.006;
  11         3020  
6 11     11   2942 use strict;
  10         19  
  10         263  
7 10     11   52 use warnings;
  10         36  
  10         654  
8              
9             our $AUTHORITY = 'cpan:TOBYINK';
10             our $VERSION = '0.024';
11              
12 10     10   4123 use Class::Tiny 0.006 ();
  10         14831  
  10         4324  
13             our @ISA = 'Class::Tiny';
14              
15             my %EXPORT_TAGS = (
16             default => [qw/ has extends with strict /],
17             all => [qw/ has extends with before after around strict warnings confess /],
18             cmm => [qw/ before after around /],
19             );
20              
21             my %CLASS_ATTRIBUTES;
22              
23             sub import
24             {
25 22     22   2575 my $me = shift;
26             my %want =
27             map +($_ => 1),
28 22 50       90 map +(@{ $EXPORT_TAGS{substr($_, 1)} or [$_] }),
  22 100       229  
29             (@_ ? @_ : '-default');
30            
31 22 50       187 strict->import if delete $want{strict};
32 22 100       105 warnings->import if delete $want{warnings};
33            
34 22         57 my $caller = caller;
35 22 50   31   150 $me->_install_tracked($caller, has => sub { unshift @_, $me, $caller; goto \&has }) if delete $want{has};
  31         9476  
  31         120  
36 22 50   5   135 $me->_install_tracked($caller, extends => sub { unshift @_, $me, $caller; goto \&extends }) if delete $want{extends};
  5         516  
  5         24  
37 22 50   2   156 $me->_install_tracked($caller, with => sub { unshift @_, $me, $caller; goto \&with }) if delete $want{with};
  2         352  
  2         9  
38 22 100       69 $me->_install_tracked($caller, confess => \&confess) if delete $want{confess};
39            
40 22         51 for my $modifier (qw/ before after around /)
41             {
42 66 100       149 next unless delete $want{$modifier};
43             $me->_install_tracked($caller, $modifier, sub
44             {
45 3     3   599 require Class::Method::Modifiers;
46 3         15 Class::Method::Modifiers::install_modifier($caller, $modifier, @_);
47 9         30 });
48             }
49            
50 22 50       68 croak("Unknown import symbols (%s)", join ", ", sort keys %want) if keys %want;
51            
52 22         53 @_ = ($me);
53 22         149 goto \&Class::Tiny::import;
54             }
55              
56             my %INSTALLED;
57             sub _install_tracked
58             {
59 10     10   82 no strict 'refs';
  10         29  
  10         2474  
60 78     78   168 my ($me, $pkg, $name, $code) = @_;
61 78         107 *{"$pkg\::$name"} = $code;
  78         302  
62 78         358 $INSTALLED{$pkg}{$name} = "$code";
63             }
64              
65             sub unimport
66             {
67 1     1   108 my $me = shift;
68 1         2 my $caller = caller;
69 1         4 $me->_clean($caller, $INSTALLED{$caller});
70             }
71              
72             sub _clean
73             {
74 2     2   6 my ($me, $target, $exports) = @_;
75 2 50       18 my %rev = reverse %$exports or return;
76 2         20 my $stash = _getstash($target);
77            
78 2         8 for my $name (keys %$exports)
79             {
80 8 50 33     26 if ($stash->{$name} and defined(&{$stash->{$name}}))
  8         30  
81             {
82 8 50       42 if ($rev{$target->can($name)})
83             {
84 8         20 my $old = delete $stash->{$name};
85 8         25 my $full_name = join('::',$target,$name);
86             # Copy everything except the code slot back into place (e.g. $has)
87 8         16 foreach my $type (qw(SCALAR HASH ARRAY IO))
88             {
89 32 100       45 next unless defined(*{$old}{$type});
  32         1548  
90 10     10   84 no strict 'refs';
  10         23  
  10         7687  
91 8         16 *$full_name = *{$old}{$type};
  8         31  
92             }
93             }
94             }
95             }
96             }
97              
98             sub croak
99             {
100 5     5 0 35 require Carp;
101 5         17 my ($fmt, @values) = @_;
102 5         1033 Carp::croak(sprintf($fmt, @values));
103             }
104              
105             sub confess
106             {
107 1     1 1 9 require Carp;
108 1         3 my ($fmt, @values) = @_;
109 1         267 Carp::confess(sprintf($fmt, @values));
110             }
111              
112             my %BUILD_WRAPPED;
113              
114             sub has
115             {
116 31     31 1 82 my ($me, $caller) = (shift, shift);
117 31         123 my ($attr, %spec) = @_;
118            
119 31 50 33     166 if (defined($attr) and ref($attr) eq q(ARRAY))
120             {
121 0         0 has($caller, $_, %spec) for @$attr;
122 0         0 return;
123             }
124              
125 31         144 $CLASS_ATTRIBUTES{$caller}{$attr} = +{ %spec };
126 31   100     160 $CLASS_ATTRIBUTES{$caller}{$attr}{is} ||= 'ro';
127 31 100 100     112 $CLASS_ATTRIBUTES{$caller}{$attr}{lazy} ||= 1 if exists($spec{default});
128            
129 31 50 33     251 if (!defined($attr) or ref($attr) or $attr !~ /^[^\W\d]\w*$/s)
      33        
130             {
131 0         0 croak("Invalid accessor name '%s'", $attr);
132             }
133            
134 31 100       94 my $init_arg = exists($spec{init_arg}) ? delete($spec{init_arg}) : \undef;
135 31   100     98 my $is = delete($spec{is}) || 'rw';
136 31         53 my $required = delete($spec{required});
137 31         56 my $default = delete($spec{default});
138 31         52 my $lazy = delete($spec{lazy});
139 31         50 my $clearer = delete($spec{clearer});
140 31         48 my $predicate = delete($spec{predicate});
141 31         54 my $setter_wrap;
142              
143 31 50 33     171 if ($spec{isa} or $spec{coerce})
144             {
145 0 0       0 ref($spec{isa}) or croak("Type names are strings are not supported");
146 0 0       0 $spec{isa}->can('check') or croak("Type doesn't have a `check` method");
147 0 0       0 $spec{isa}->can('get_message') or croak("Type doesn't have a `get_message` method");
148 0 0 0     0 $spec{isa}->can('coerce') or !$spec{coerce} or croak("Type doesn't have a `coerce` method");
149 0         0 $setter_wrap = 1;
150 0         0 delete $spec{$_} for qw/ isa coerce /;
151 0 0       0 __PACKAGE__->_wrap_build($caller) unless $BUILD_WRAPPED{$caller}++;
152             }
153            
154 31 50       77 if ($is eq 'lazy')
155             {
156 0         0 $lazy = 1;
157 0         0 $is = 'ro';
158             }
159            
160 31 100 100     125 if (defined $lazy and not $lazy)
    50          
161             {
162 2         7 croak("Class::Tiny does not support eager defaults");
163             }
164             elsif (keys %spec)
165             {
166 0         0 croak("Unknown options in attribute specification (%s)", join ", ", sort keys %spec);
167             }
168            
169 29 100 100     109 if ($required and 'Class::Tiny::Object'->can('new') == $caller->can('new'))
170             {
171 1         4 croak("Class::Tiny::Object::new does not support required attributes; please manually override the constructor to enforce required attributes");
172             }
173            
174 28 100 100     163 if ($init_arg and ref($init_arg) eq 'SCALAR' and not defined $$init_arg)
    100 66        
      100        
175             {
176             # ok
177             }
178             elsif (!$init_arg or $init_arg ne $attr)
179             {
180 2         6 croak("Class::Tiny does not support init_arg");
181             }
182            
183 26         68 my $getter = "\$_[0]{'$attr'}";
184 26 100 100     109 if (defined $default and ref($default) eq 'CODE')
    100          
185             {
186 6         14 $getter = "\$_[0]{'$attr'} = \$default->(\$_[0]) unless exists \$_[0]{'$attr'}; $getter";
187             }
188             elsif (defined $default)
189             {
190 9         25 $getter = "\$_[0]{'$attr'} = \$default unless exists \$_[0]{'$attr'}; $getter";
191             }
192            
193 26         47 my $setter_name;
194             my @methods;
195 26         45 my $needs_clean = 0;
196 26 100 100     82 if ($is eq 'rw')
    100          
    50          
197             {
198 13         25 $setter_name = $attr;
199 13         48 push @methods, "sub $attr :method { \$_[0]{'$attr'} = \$_[1] if \@_ > 1; $getter };";
200             }
201             elsif ($is eq 'ro' or $is eq 'rwp')
202             {
203 12         33 $setter_name = "_set_$attr";
204 12         39 push @methods, "sub $attr :method { $getter };";
205 12 100       35 push @methods, "sub _set_$attr :method { \$_[0]{'$attr'} = \$_[1] };"
206             if $is eq 'rwp';
207             }
208             elsif ($is eq 'bare')
209             {
210 10     10   83 no strict 'refs';
  10         22  
  10         2283  
211 1         3 $needs_clean = not exists &{"$caller\::$attr"};
  1         4  
212             }
213             else
214             {
215 0         0 croak("Class::Tiny::Antlers does not support '$is' accessors");
216             }
217            
218 26 100       71 if ($clearer)
219             {
220 3 100       16 $clearer = ($attr =~ /^_/) ? "_clear$attr" : "clear_$attr" if $clearer eq '1';
    100          
221 3         9 push @methods, "sub $clearer :method { delete(\$_[0]{'$attr'}) }";
222             }
223            
224 26 100       58 if ($predicate)
225             {
226 4 100       17 $predicate = ($attr =~ /^_/) ? "_has$attr" : "has_$attr" if $predicate eq '1';
    100          
227 4         11 push @methods, "sub $predicate :method { exists(\$_[0]{'$attr'}) }";
228             }
229            
230 26 100   5   2873 eval "package $caller; @methods";
  5 100   12   4034  
  12 100   11   16854  
  13 100   16   9516  
  11 100   18   8820  
  16 100   12   5328  
  19 100   3   28952  
  13 100       2954  
  14         15861  
  9         10333  
  7         44  
  7         3020  
  7         7005  
  4         15  
  4         36  
231 26         237 $me->create_attributes($caller, $attr);
232            
233 26 50       1073 $me->_wrap_setter($caller, $attr, $setter_name) if $setter_wrap;
234            
235 10 100   10   197 $me->_clean($caller, { $attr => do { no strict 'refs'; ''.\&{"$caller\::$attr"} } })
  10         21  
  10         1981  
  26         112  
  1         2  
  1         9  
236             if $needs_clean;
237             }
238              
239             sub _wrap_build {
240 0     0   0 my ($me, $caller) = @_;
241 10     10   86 no strict 'refs';
  10         37  
  10         3158  
242 0 0       0 if (exists &{"$caller\::BUILD"}) {
  0         0  
243 0         0 my $next = \&{"$caller\::BUILD"};
  0         0  
244 0         0 $me->_clean($caller, { BUILD => $next });
245 0         0 eval sprintf(q{
246             package %s;
247             sub BUILD {
248             my $self = shift;
249             %s->_check_args('%s', @_);
250             $self->$next(@_);
251             }
252             }, $caller, $me, $caller);
253             }
254             else {
255 0         0 eval sprintf(q{
256             package %s;
257             sub BUILD {
258             my $self = shift;
259             %s->_check_args('%s', $self, @_);
260             }
261             }, $caller, $me, $caller);
262             }
263             }
264              
265             sub _check_args {
266 0     0   0 my ($me, $caller, $object, $args) = @_;
267 0         0 my $spec = $CLASS_ATTRIBUTES{$caller};
268 0         0 for my $attr (sort keys %$spec) {
269 0 0       0 my $type = $spec->{$attr}{isa} or next;
270 0 0       0 exists $args->{$attr} or next;
271 0 0       0 $type->check($args->{$attr}) and next;
272 0 0       0 if ($spec->{$attr}{coerce}) {
273 0         0 my $coerced = $type->coerce($args->{$attr});
274 0 0       0 if ($type->check($coerced)) {
275 0         0 $object->{$attr} = $args->{$attr} = $coerced;
276 0         0 next;
277             }
278             }
279 0         0 croak('Type constraint check failed for attribute "%s": %s', $attr, $type->get_message($args->{$attr}));
280             }
281             }
282              
283             sub _wrap_setter {
284 0     0   0 my ($me, $caller, $attr, $setter_name) = @_;
285 10     10   95 no strict 'refs';
  10         21  
  10         2906  
286 0         0 my $next = \&{"$caller\::$setter_name"};
  0         0  
287 0         0 my $spec = $CLASS_ATTRIBUTES{$caller};
288 0         0 my $type = $spec->{$attr}{isa};
289 0         0 my $coerce = $spec->{$attr}{coerce};
290 0         0 $me->_clean($caller, { $setter_name => $next });
291 0 0 0     0 if ($coerce) {
    0          
292 0         0 eval sprintf(q{
293             package %s;
294             sub %s {
295             my $self = shift;
296             if (@_) {
297             $type->check(@_)
298             or do {
299             my $coerced = $type->coerce(@_);
300             $type->check($coerced) and do { @_ = ($coerced); 1 };
301             }
302             or %s::croak('Type constraint check failed for attribute "%s": %%s', $type->get_message(@_));
303             }
304             $self->$next(@_);
305             }
306             }, $caller, $setter_name, $me, $attr);
307             }
308             elsif ($type->can('can_be_inlined') && $type->can_be_inlined) {
309 0   0     0 my $ic = $type->can('inline_check') || $type->can('_inline_check');
310 0         0 eval sprintf(q{
311             package %s;
312             sub %s {
313             my $self = shift;
314             if (@_) {
315             my $val = $_[0];
316             %s or %s::croak('Type constraint check failed for attribute "%s": %%s', $type->get_message(@_));
317             }
318             $self->$next(@_);
319             }
320             }, $caller, $setter_name, $type->$ic('$val'), $me, $attr);
321             }
322             else {
323 0         0 eval sprintf(q{
324             package %s;
325             sub %s {
326             my $self = shift;
327             if (@_) {
328             $type->check(@_) or %s::croak('Type constraint check failed for attribute "%s": %%s', $type->get_message(@_));
329             }
330             $self->$next(@_);
331             }
332             }, $caller, $setter_name, $me, $attr);
333             }
334             }
335              
336             sub extends
337             {
338 5     5 1 18 my ($me, $caller) = (shift, shift);
339 5         15 my (@parents) = @_;
340            
341 5         15 for my $parent (@parents)
342             {
343 6         289 eval "require $parent";
344             }
345            
346 10     10   87 no strict 'refs';
  10         42  
  10         3224  
347 5         20 @{"$caller\::ISA"} = @parents;
  5         129  
348             }
349              
350             sub with
351             {
352 2     2 1 6 my ($me, $caller) = (shift, shift);
353 2         467 require Role::Tiny::With;
354 2         291 goto \&Role::Tiny::With::with;
355             }
356              
357             sub get_all_attribute_specs_for
358             {
359 0     0 1 0 my $me = shift;
360 0         0 my $class = $_[0];
361            
362 0         0 my %specs = %{ $me->get_all_attribute_defaults_for };
  0         0  
363             $specs{$_} =
364             defined($specs{$_})
365             ? +{ is => 'rw', lazy => 1, default => $specs{$_} }
366             : +{ is => 'rw' }
367 0 0       0 for keys %specs;
368            
369 0         0 for my $p ( reverse @{ $class->mro::get_linear_isa } )
  0         0  
370             {
371 0 0       0 while ( my ($k, $v) = each %{$CLASS_ATTRIBUTES{$p}||{}} )
  0         0  
372             {
373 0         0 $specs{$k} = $v;
374             }
375             }
376            
377 0         0 \%specs;
378             }
379              
380             1;
381              
382              
383             __END__