File Coverage

blib/lib/Acme/Has/Tiny.pm
Criterion Covered Total %
statement 145 173 83.8
branch 29 60 48.3
condition 13 30 43.3
subroutine 30 31 96.7
pod 3 3 100.0
total 220 297 74.0


line stmt bran cond sub pod time code
1             package Acme::Has::Tiny;
2              
3 2     2   239884 use 5.008;
  2         6  
  2         73  
4 2     2   12 use strict;
  2         3  
  2         64  
5 2     2   10 use warnings;
  2         9  
  2         68  
6 2     2   9 no warnings qw(uninitialized once void numeric);
  2         4  
  2         141  
7              
8             our $AUTHORITY = "cpan:TOBYINK";
9             our $VERSION = "0.002";
10              
11 2     2   10 use B qw(perlstring);
  2         3  
  2         131  
12 2     2   10 use Scalar::Util qw(blessed);
  2         3  
  2         326  
13              
14             our %ATTRIBUTES;
15             our %VALIDATORS;
16              
17             sub _croak ($;@)
18             {
19 2     2   13 my $msg = shift;
20 2         12 require Carp;
21 2         5 $Carp::CarpInternal{+__PACKAGE__} = 1;
22 2         391 Carp::croak(sprintf($msg, @_));
23             }
24              
25 2 50   2   154 BEGIN { *CAN_HAZ_XS = eval 'use Class::XSAccessor 1.18; 1' ? sub(){!!1} : sub(){!!0} };
  2     2   1864  
  2         5789  
  2         11  
26              
27             sub import
28             {
29 2     2   11 no strict qw(refs);
  2         4  
  2         1215  
30            
31 4     4   90 my $me = shift;
32 4         10 my $caller = caller;
33 4         18 my %want = map +($_ => 1), @_;
34            
35 4 50       17 if ($want{has})
36             {
37 4     8   11 *{"$caller\::has"} = sub { unshift @_, __PACKAGE__; goto \&has };
  4         21  
  8         478  
  8         28  
38             }
39            
40 4 100       13 if ($want{new})
41             {
42 2         6 *{"$caller\::new"} = sub {
43 2     2   2143 my $new = $me->create_constructor("new", class => $_[0], replace => 1);
44 2         7 goto $new;
45 2         5 };
46             }
47            
48 4         241 return;
49             }
50              
51             sub has
52             {
53 8     8 1 13 my $me = shift;
54 8         24 my ($attrs, %options) = @_;
55 8 50       31 $attrs = [$attrs] unless ref($attrs) eq q(ARRAY);
56            
57 8   33     42 my $class = $options{class} || caller;
58 8         14 delete $VALIDATORS{$class};
59            
60 8         21 my @code = "package $class;";
61 8         16 for my $a (@$attrs)
62             {
63 8         35 $ATTRIBUTES{$class}{$a} = +{ %options };
64 8         30 push @code, $me->_build_methods($class, $a, $ATTRIBUTES{$class}{$a});
65             }
66 8         21 my $str = join "\n", @code, "1;";
67            
68 2 50 66 2   11 eval($str) or die("COMPILE ERROR: $@\nCODE:\n$str\n");
  2 100   4   4  
  2         210  
  8         496  
  4         15977  
  4         60  
  2         12  
69 8         44 return;
70             }
71              
72             sub assert_valid
73             {
74 6     6 1 10 my $me = shift;
75 6         13 my ($class, $hash) = @_;
76            
77 10   66     542 my @validator = map {
78 6         24 $VALIDATORS{$_} ||= $me->_compile_validator($_, $ATTRIBUTES{$_});
79             } $me->_find_parents($class);
80            
81 6         451 $_->($hash) for @validator;
82 4         102 return $hash;
83             }
84              
85             my $default_buildargs = sub
86             {
87             my $class = shift;
88             return +{
89             (@_ == 1 && ref($_[0]) eq q(HASH)) ? %{$_[0]} : @_
90             };
91             };
92              
93             sub create_constructor
94             {
95 6     6 1 25 my $me = shift;
96 6         19 my ($method, %options) = @_;
97            
98 6   66     27 my $class = $options{class} || caller;
99 6         24 my $build = $options{build};
100 6   66     29 my $buildargs = $options{buildargs} || $default_buildargs;
101            
102             my $code = sub
103             {
104 6     6   2657 my $class = shift;
105 6         25 my $self = bless($class->$buildargs(@_), $class);
106 6         41 $me->assert_valid($class, $self);
107 4 50       15 $self->$build if $options{build};
108 4         12 return $self;
109 6         41 };
110            
111 2     2   19 no strict qw(refs);
  2         19  
  2         180  
112 6 100       19 if ($options{replace})
113             {
114 2     2   16 no warnings qw(redefine);
  2         4  
  2         101  
115 2         5 *{"$class\::$method"} = $code;
  2         12  
116             }
117             else
118             {
119 2     2   9 use warnings FATAL => qw(redefine);
  2         3  
  2         633  
120 4         4 *{"$class\::$method"} = $code;
  4         25  
121             }
122 6         18 return $code;
123             }
124              
125             sub _build_methods
126             {
127 8     8   11 my $me = shift;
128 8         12 my ($class, $attr, $spec) = @_;
129 8         10 my @code;
130            
131 8 100       28 if ($spec->{is} eq q(rwp))
    50          
132             {
133 2         6 push @code,
134             $me->_build_reader($class, $attr, $spec, $attr),
135             $me->_build_writer($class, $attr, $spec, "_set_$attr");
136             }
137             elsif ($spec->{is} eq q(rw))
138             {
139 0         0 push @code, $me->_build_accessor($class, $attr, $spec, $attr);
140             }
141             else
142             {
143 6         15 push @code, $me->_build_reader($class, $attr, $spec, $attr);
144             }
145            
146 8 100       35 if ($spec->{predicate} eq q(1))
    50          
147             {
148 4         23 push @code, $me->_build_predicate($class, $attr, $spec, "has_$attr");
149             }
150             elsif ($spec->{predicate})
151             {
152 0         0 push @code, $me->_build_predicate($class, $attr, $spec, $spec->{predicate});
153             }
154            
155 8         27 return @code;
156             }
157              
158             sub _build_reader
159             {
160 8     8   11 my $me = shift;
161 8         14 my ($class, $attr, $spec, $method) = @_;
162            
163 8         8 my $builder_name;
164 8 50       35 if ($spec->{builder} eq q(1))
    50          
    50          
165             {
166 0         0 $builder_name = "_build_$attr";
167             }
168             elsif (ref($spec->{builder}) eq q(CODE))
169             {
170 2     2   9 no strict qw(refs);
  2         4  
  2         2009  
171 0         0 $builder_name = "_build_$attr";
172 0         0 *{"$class\::$builder_name"} = $spec->{builder};
  0         0  
173             }
174             elsif ($spec->{builder})
175             {
176 0         0 $builder_name = $spec->{builder};
177             }
178            
179 8 50       20 if (CAN_HAZ_XS and not $builder_name)
180             {
181 8         33 "Class::XSAccessor"->import(class => $class, getters => { $method => $attr });
182 8         1040 return;
183             }
184            
185 0 0       0 return $builder_name
186             ? sprintf('sub %s { $_[0]{%s} ||= $_[0]->%s }', $method, perlstring($attr), $builder_name)
187             : sprintf('sub %s { $_[0]{%s} }', $method, perlstring($attr));
188             }
189              
190             sub _build_predicate
191             {
192 4     4   7 my $me = shift;
193 4         10 my ($class, $attr, $spec, $method) = @_;
194            
195 4         5 if (CAN_HAZ_XS)
196             {
197 4         20 "Class::XSAccessor"->import(class => $class, exists_predicates => { $method => $attr });
198 4         514 return;
199             }
200            
201 0         0 return sprintf('sub %s { exists $_[0]{%s} }', $method, perlstring($attr));
202             }
203              
204             sub _build_writer
205             {
206 2     2   4 my $me = shift;
207 2         14 my ($class, $attr, $spec, $method) = @_;
208            
209 2         4 my $inlined;
210 2         3 my $isa = $spec->{isa};
211 2 50 33     23 if (blessed($isa) and $isa->isa('Type::Tiny') and $isa->can_be_inlined)
    0 33        
212             {
213 2         124 $inlined = $isa->inline_assert('$_[1]');
214             }
215             elsif ($isa)
216             {
217 0         0 $inlined = sprintf('$Acme::Has::Tiny::ATTRIBUTES{%s}{%s}{isa}->($_[1]);', perlstring($class), perlstring($attr));
218             }
219            
220 2 50       252 if (CAN_HAZ_XS and not $inlined)
221             {
222 0         0 "Class::XSAccessor"->import(class => $class, setters => { $method => $attr });
223 0         0 return;
224             }
225            
226 2 50       27 return defined($inlined)
227             ? sprintf('sub %s { %s; $_[0]{%s} = $_[1] }', $method, $inlined, perlstring($attr))
228             : sprintf('sub %s { $_[0]{%s} = $_[1] }', $method, perlstring($attr));
229             }
230              
231             sub _build_accessor
232             {
233 0     0   0 my $me = shift;
234 0         0 my ($class, $attr, $spec, $method) = @_;
235            
236 0         0 my $inlined;
237 0         0 my $isa = $spec->{isa};
238 0 0 0     0 if (blessed($isa) and $isa->can_be_inlined)
    0          
239             {
240 0         0 $inlined = $isa->inline_assert('$_[1]');
241             }
242             elsif ($isa)
243             {
244 0         0 $inlined = sprintf('$Acme::Has::Tiny::ATTRIBUTES{%s}{%s}{isa}->($_[1]);', perlstring($class), perlstring($attr));
245             }
246            
247 0 0       0 if (CAN_HAZ_XS and not $inlined)
248             {
249 0         0 "Class::XSAccessor"->import(class => $class, accessors => { $method => $attr });
250 0         0 return;
251             }
252            
253 0 0       0 return defined($inlined)
254             ? sprintf('sub %s { return $_[0]{%s} unless @_; %s; $_[0]{%s} = $_[1] }', $method, perlstring($attr), $inlined, perlstring($attr))
255             : sprintf('sub %s { return $_[0]{%s} unless @_; $_[0]{%s} = $_[1] }', $method, perlstring($attr), perlstring($attr));
256             }
257              
258             sub _compile_validator
259             {
260 4     4   8 my $me = shift;
261 4         24 my $code = join "\n" => (
262             "#line 1 \"validator(Acme::Has::Tiny)\"",
263             "package $_[0];",
264             'sub {',
265             'my $self = $_[0];',
266             $me->_build_validator_parts(@_),
267             'return $self;',
268             '}',
269             );
270 4         759 eval $code;
271             }
272              
273             sub _build_validator_parts
274             {
275 4     4   7 my $me = shift;
276 4         8 my ($class, $attributes) = @_;
277            
278 4         7 my @code;
279 4         27 for my $a (sort keys %$attributes)
280             {
281 8         402 my $spec = $attributes->{$a};
282            
283 8 50       34 if ($spec->{default})
    100          
284             {
285 0         0 push @code, sprintf(
286             'exists($self->{%s}) or $self->{%s} = $Acme::Has::Tiny::ATTRIBUTES{%s}{%s}{default}->();',
287             map perlstring($_), $a, $a, $class, $a,
288             );
289             }
290             elsif ($spec->{required})
291             {
292 4         37 push @code, sprintf(
293             'exists($self->{%s}) or Acme::Has::Tiny::_croak("Attribute %%s is required by %%s", %s, %s);',
294             map perlstring($_), $a, $a, $class,
295             );
296             }
297            
298 8         16 my $isa = $spec->{isa};
299 8 50 33     71 if (blessed($isa) and $isa->can_be_inlined)
    0          
300             {
301 8         357 push @code, (
302             sprintf('if (exists($self->{%s})) {', $a),
303             $isa->inline_assert(sprintf '$self->{%s}', perlstring($a)),
304             '}',
305             );
306             }
307             elsif ($isa)
308             {
309 0         0 push @code, (
310             sprintf('if (exists($self->{%s})) {', $a),
311             sprintf('$Acme::Has::Tiny::ATTRIBUTES{%s}{%s}{isa}->($self->{%s});', map perlstring($_), $class, $a, $a),
312             '}',
313             );
314             }
315             }
316            
317 4         279 return @code;
318             }
319              
320             sub _find_parents
321             {
322 6     6   13 my $me = shift;
323 6         11 my $class = $_[0];
324            
325 6 50 33     9 if (eval { require mro } or eval { require MRO::Compat })
  6         1862  
  0         0  
326             {
327 6         1414 return @{ mro::get_linear_isa($class) };
  6         43  
328             }
329            
330 0           require Class::ISA;
331 0           return Class::ISA::self_and_super_path($class);
332             }
333              
334             1;
335              
336             __END__