File Coverage

blib/lib/Class/Tiny/Antlers.pm
Criterion Covered Total %
statement 147 166 88.5
branch 77 96 80.2
condition 31 42 73.8
subroutine 30 31 96.7
pod 5 6 83.3
total 290 341 85.0


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