File Coverage

blib/lib/Class/Tiny/Antlers.pm
Criterion Covered Total %
statement 144 163 88.3
branch 77 96 80.2
condition 31 42 73.8
subroutine 29 30 96.6
pod 5 6 83.3
total 286 337 84.8


line stmt bran cond sub pod time code
1             package Class::Tiny::Antlers;
2              
3 2     2   8 sub _getstash { \%{"$_[0]::"} }
  2         8  
4              
5 11     11   802225 use strict;
  11         1152  
  11         1540  
6 10     11   56 use warnings;
  10         17  
  10         789  
7              
8             our $AUTHORITY = 'cpan:TOBYINK';
9             our $VERSION = '0.022';
10              
11 10     11   8365 use Class::Tiny 0.006 ();
  10         44328  
  10         5244  
12             our @ISA = 'Class::Tiny';
13              
14             my %EXPORT_TAGS = (
15             default => [qw/ has extends with strict /],
16             all => [qw/ has extends with before after around strict warnings confess /],
17             cmm => [qw/ before after around /],
18             );
19              
20             my %CLASS_ATTRIBUTES;
21              
22             sub import
23             {
24 22     22   3165 my $class = shift;
25 22 50       434 my %want =
26             map +($_ => 1),
27 22 100       90 map +(@{ $EXPORT_TAGS{substr($_, 1)} or [$_] }),
28             (@_ ? @_ : '-default');
29            
30 22 50       365 strict->import if delete $want{strict};
31 22 100       138 warnings->import if delete $want{warnings};
32            
33 22         51 my $caller = caller;
34 22 50   31   152 _install_tracked($caller, has => sub { unshift @_, $caller; goto \&has }) if delete $want{has};
  31         6829  
  31         104  
35 22 50   5   156 _install_tracked($caller, extends => sub { unshift @_, $caller; goto \&extends }) if delete $want{extends};
  5         418  
  5         24  
36 22 50   2   134 _install_tracked($caller, with => sub { unshift @_, $caller; goto \&with }) if delete $want{with};
  2         249  
  2         10  
37 22 100       65 _install_tracked($caller, confess => \&confess) if delete $want{confess};
38            
39 22         44 for my $modifier (qw/ before after around /)
40             {
41 66 100       179 next unless delete $want{$modifier};
42             _install_tracked($caller, $modifier, sub
43             {
44 3     3   587 require Class::Method::Modifiers;
45 3         12 Class::Method::Modifiers::install_modifier($caller, $modifier, @_);
46 9         36 });
47             }
48            
49 22 50       77 croak("Unknown import symbols (%s)", join ", ", sort keys %want) if keys %want;
50            
51 22         54 @_ = ($class);
52 22         112 goto \&Class::Tiny::import;
53             }
54              
55             my %INSTALLED;
56             sub _install_tracked
57             {
58 10     10   77 no strict 'refs';
  10         21  
  10         2681  
59 78     78   128 my ($pkg, $name, $code) = @_;
60 78         96 *{"$pkg\::$name"} = $code;
  78         326  
61 78         280 $INSTALLED{$pkg}{$name} = "$code";
62             }
63              
64             sub unimport
65             {
66 1     1   79 shift;
67 1         1 my $caller = caller;
68 1         3 _clean($caller, $INSTALLED{$caller});
69             }
70              
71             sub _clean
72             {
73 2     2   3 my ($target, $exports) = @_;
74 2 50       20 my %rev = reverse %$exports or return;
75 2         8 my $stash = _getstash($target);
76            
77 2         8 for my $name (keys %$exports)
78             {
79 8 50 33     25 if ($stash->{$name} and defined(&{$stash->{$name}}))
  8         38  
80             {
81 8 50       57 if ($rev{$target->can($name)})
82             {
83 8         21 my $old = delete $stash->{$name};
84 8         27 my $full_name = join('::',$target,$name);
85             # Copy everything except the code slot back into place (e.g. $has)
86 8         19 foreach my $type (qw(SCALAR HASH ARRAY IO))
87             {
88 32 100       39 next unless defined(*{$old}{$type});
  32         2191  
89 10     10   53 no strict 'refs';
  10         27  
  10         10272  
90 8         12 *$full_name = *{$old}{$type};
  8         35  
91             }
92             }
93             }
94             }
95             }
96              
97             sub croak
98             {
99 5     5 0 43 require Carp;
100 5         10 my ($fmt, @values) = @_;
101 5         1206 Carp::croak(sprintf($fmt, @values));
102             }
103              
104             sub confess
105             {
106 1     1 1 8 require Carp;
107 1         2 my ($fmt, @values) = @_;
108 1         359 Carp::confess(sprintf($fmt, @values));
109             }
110              
111             sub has
112             {
113 31     31 1 56 my $caller = shift;
114 31         132 my ($attr, %spec) = @_;
115            
116 31         137 $CLASS_ATTRIBUTES{$caller}{$attr} = +{ %spec };
117 31   100     146 $CLASS_ATTRIBUTES{$caller}{$attr}{is} ||= 'ro';
118 31 100 100     120 $CLASS_ATTRIBUTES{$caller}{$attr}{lazy} ||= 1 if exists($spec{default});
119            
120 31 50 33     156 if (defined($attr) and ref($attr) eq q(ARRAY))
121             {
122 0         0 has($caller, $_, %spec) for @$attr;
123 0         0 return;
124             }
125              
126 31 50 33     343 if (!defined($attr) or ref($attr) or $attr !~ /^[^\W\d]\w*$/s)
      33        
127             {
128 0         0 croak("Invalid accessor name '%s'", $attr);
129             }
130            
131 31 100       174 my $init_arg = exists($spec{init_arg}) ? delete($spec{init_arg}) : \undef;
132 31   100     9568 my $is = delete($spec{is}) || 'rw';
133 31         59 my $required = delete($spec{required});
134 31         45 my $default = delete($spec{default});
135 31         42 my $lazy = delete($spec{lazy});
136 31         44 my $clearer = delete($spec{clearer});
137 31         38 my $predicate = delete($spec{predicate});
138            
139 31 50       72 if ($is eq 'lazy')
140             {
141 0         0 $lazy = 1;
142 0         0 $is = 'ro';
143             }
144            
145 31 100 100     274 if (defined $lazy and not $lazy)
    50 33        
    50          
146             {
147 2         7 croak("Class::Tiny does not support eager defaults");
148             }
149             elsif ($spec{isa} or $spec{coerce})
150             {
151 0         0 croak("Class::Tiny does not support type constraints");
152             }
153             elsif (keys %spec)
154             {
155 0         0 croak("Unknown options in attribute specification (%s)", join ", ", sort keys %spec);
156             }
157            
158 29 100 100     110 if ($required and 'Class::Tiny::Object'->can('new') == $caller->can('new'))
159             {
160 1         5 croak("Class::Tiny::Object::new does not support required attributes; please manually override the constructor to enforce required attributes");
161             }
162            
163 28 100 100     230 if ($init_arg and ref($init_arg) eq 'SCALAR' and not defined $$init_arg)
    100 66        
      100        
164             {
165             # ok
166             }
167             elsif (!$init_arg or $init_arg ne $attr)
168             {
169 2         7 croak("Class::Tiny does not support init_arg");
170             }
171            
172 26         56 my $getter = "\$_[0]{'$attr'}";
173 26 100 100     128 if (defined $default and ref($default) eq 'CODE')
    100          
174             {
175 6         9 $getter = "\$_[0]{'$attr'} = \$default->(\$_[0]) unless exists \$_[0]{'$attr'}; $getter";
176             }
177             elsif (defined $default)
178             {
179 9         23 $getter = "\$_[0]{'$attr'} = \$default unless exists \$_[0]{'$attr'}; $getter";
180             }
181            
182 26         30 my @methods;
183 26         33 my $needs_clean = 0;
184 26 100 100     105 if ($is eq 'rw')
    100          
    50          
185             {
186 13         44 push @methods, "sub $attr :method { \$_[0]{'$attr'} = \$_[1] if \@_ > 1; $getter };";
187             }
188             elsif ($is eq 'ro' or $is eq 'rwp')
189             {
190 12         33 push @methods, "sub $attr :method { $getter };";
191 12 100       45 push @methods, "sub _set_$attr :method { \$_[0]{'$attr'} = \$_[1] };"
192             if $is eq 'rwp';
193             }
194             elsif ($is eq 'bare')
195             {
196 10     10   66 no strict 'refs';
  10         23  
  10         3907  
197 1         4 $needs_clean = not exists &{"$caller\::$attr"};
  1         5  
198             }
199             else
200             {
201 0         0 croak("Class::Tiny::Antlers does not support '$is' accessors");
202             }
203            
204 26 100       56 if ($clearer)
205             {
206 3 100       15 $clearer = ($attr =~ /^_/) ? "_clear$attr" : "clear_$attr" if $clearer eq '1';
    100          
207 3         18 push @methods, "sub $clearer :method { delete(\$_[0]{'$attr'}) }";
208             }
209            
210 26 100       56 if ($predicate)
211             {
212 4 100       17 $predicate = ($attr =~ /^_/) ? "_has$attr" : "has_$attr" if $predicate eq '1';
    100          
213 4         13 push @methods, "sub $predicate :method { exists(\$_[0]{'$attr'}) }";
214             }
215            
216 26 100   5   2824 eval "package $caller; @methods";
  5 100   12   2549  
  12 100   11   10894  
  13 100   16   4781  
  11 100   18   4176  
  16 100   12   2177  
  19 100   3   14288  
  13 100       1083  
  14         7672  
  9         4676  
  7         34  
  7         1228  
  7         3309  
  4         14  
  4         16  
217 26         167 __PACKAGE__->create_attributes($caller, $attr);
218            
219 10 100   10   67 _clean($caller, { $attr => do { no strict 'refs'; ''.\&{"$caller\::$attr"} } })
  10         29  
  10         1893  
  26         975  
  1         3  
  1         10  
220             if $needs_clean;
221             }
222              
223             sub extends
224             {
225 5     5 1 14 my $caller = shift;
226 5         15 my (@parents) = @_;
227            
228 5         38 for my $parent (@parents)
229             {
230 6         433 eval "require $parent";
231             }
232            
233 10     10   57 no strict 'refs';
  10         18  
  10         3345  
234 5         15 @{"$caller\::ISA"} = @parents;
  5         162  
235             }
236              
237             sub with
238             {
239 2     2 1 5 my $caller = shift;
240 2         1165 require Role::Tiny::With;
241 2         378 goto \&Role::Tiny::With::with;
242             }
243              
244             sub get_all_attribute_specs_for
245             {
246 0     0 1 0 my $me = shift;
247 0         0 my $class = $_[0];
248            
249 0         0 my %specs = %{ $me->get_all_attribute_defaults_for };
  0         0  
250             $specs{$_} =
251             defined($specs{$_})
252             ? +{ is => 'rw', lazy => 1, default => $specs{$_} }
253             : +{ is => 'rw' }
254 0 0       0 for keys %specs;
255            
256 0         0 for my $p ( reverse @{ $class->mro::get_linear_isa } )
  0         0  
257             {
258 0 0       0 while ( my ($k, $v) = each %{$CLASS_ATTRIBUTES{$p}||{}} )
  0         0  
259             {
260 0         0 $specs{$k} = $v;
261             }
262             }
263            
264 0         0 \%specs;
265             }
266              
267             1;
268              
269              
270             __END__