File Coverage

blib/lib/Object/Proto/Sugar.pm
Criterion Covered Total %
statement 313 326 96.0
branch 121 142 85.2
condition 47 54 87.0
subroutine 57 59 96.6
pod n/a
total 538 581 92.6


line stmt bran cond sub pod time code
1             package Object::Proto::Sugar;
2              
3 21     21   1836182 use 5.008003;
  21         67  
4 21     21   89 use strict;
  21         39  
  21         552  
5 21     21   103 use warnings;
  21         74  
  21         1063  
6 21     21   8582 use BEGIN::Lift;
  21         119884  
  21         645  
7 21     21   143 use Devel::Hook;
  21         36  
  21         474  
8 21     21   9596 use Object::Proto;
  21         9817  
  21         1365  
9 21     21   127 use Carp qw/croak/;
  21         94  
  21         1550  
10              
11             our $VERSION = 0.04;
12              
13 21     21   214 use constant ro => 'ro';
  21         58  
  21         2326  
14 21     21   102 use constant is_ro => ( is => ro );
  21         44  
  21         1208  
15 21     21   130 use constant rw => 'rw';
  21         32  
  21         1053  
16 21     21   157 use constant is_rw => ( is => rw );
  21         31  
  21         936  
17 21     21   89 use constant nan => undef;
  21         36  
  21         805  
18 21     21   71 use constant lzy => ( lazy => 1 );
  21         35  
  21         756  
19 21     21   69 use constant bld => ( builder => 1 );
  21         38  
  21         738  
20 21     21   193 use constant lzy_bld => ( lazy_build => 1 );
  21         56  
  21         991  
21 21     21   111 use constant trg => ( trigger => 1 );
  21         55  
  21         884  
22 21     21   123 use constant clr => ( clearer => 1 );
  21         35  
  21         859  
23 21     21   83 use constant req => ( required => 1 );
  21         38  
  21         978  
24 21     21   82 use constant coe => ( coerce => 1 );
  21         25  
  21         985  
25 21     21   76 use constant lzy_hash => (lazy => 1, isa => 'HashRef', default => {} );
  21         27  
  21         1050  
26 21     21   89 use constant lzy_array => (lazy => 1, isa => 'ArrayRef', default => [] );
  21         26  
  21         1101  
27 21     21   89 use constant lzy_str => (lazy => 1, isa => 'Str', default => "");
  21         33  
  21         988  
28 21     21   84 use constant dhash => (isa => 'HashRef', default => {});
  21         24  
  21         1003  
29 21     21   84 use constant darray => (isa => 'ArrayRef', default => []);
  21         33  
  21         1034  
30 21     21   96 use constant dstr => (isa => 'Str', default => "");
  21         32  
  21         4015  
31              
32             our (%valid_types, @type_list, %valid_constants, %modifier_dispatch);
33              
34             BEGIN {
35 21     21   63 @type_list = @{ Object::Proto::list_types() };
  21         276  
36 21         103 %valid_types = map { $_ => 1 } @type_list;
  210         418  
37 21         56 %valid_constants = map { $_ => 1 } qw(
  378         519  
38             ro rw is_ro is_rw nan
39             lzy bld lzy_bld trg clr req coe
40             lzy_hash lzy_array lzy_str dhash darray dstr
41             );
42 21         1405 %modifier_dispatch = (
43             before => \&Object::Proto::before,
44             after => \&Object::Proto::after,
45             around => \&Object::Proto::around,
46             );
47             }
48              
49             sub import {
50 27     27   253 my ($pkg, @import) = @_;
51 21     21   115 no strict 'refs';
  21         47  
  21         24834  
52 27         82 my $caller = caller();
53 27         57 my (@spec, @modifiers, @extends, @with, @requires, $is_role);
54 27 100       98 $is_role = 1 if grep { $_ eq '-role' } @import;
  8         42  
55              
56 27 50       61 if (grep { $_ eq '-types' } @import) {
  8         16  
57 0     0   0 *{"${caller}::${_}"} = sub { $_ } for @type_list;
  0         0  
  0         0  
58             }
59 27 100       54 if (grep { $_ eq '-constants' } @import) {
  8         15  
60 2         8 *{"${caller}::${_}"} = \&{"Object::Proto::Sugar::${_}"} for keys %valid_constants;
  36         54  
  36         55  
61             }
62              
63 27         64 for my $name (@import) {
64 8 100       18 next if $name =~ /^-/;
65 5 50       9 if ($name =~ /^[A-Z]/) {
66             croak "Unknown type '$name' requested from Object::Proto::Sugar"
67 0 0       0 unless $valid_types{$name};
68 0     0   0 *{"${caller}::${name}"} = sub { $name };
  0         0  
  0         0  
69             } else {
70             croak "Unknown constant '$name' requested from Object::Proto::Sugar"
71 5 50       8 unless $valid_constants{$name};
72 5         4 *{"${caller}::${name}"} = \&{"Object::Proto::Sugar::${name}"};
  5         11  
  5         11  
73             }
74             }
75              
76             BEGIN::Lift::install(
77             ($caller, 'has') => sub {
78 77     77   1582 my ($name, %params) = @_;
79 77 100       147 if (ref $name) {
80 3         3 for (@{$name}) {
  3         8  
81 9         822 push @spec, $_, \%params;
82             }
83             } else {
84 74         14888 push @spec, $name, \%params;
85             }
86             }
87 27         217 );
88              
89             BEGIN::Lift::install(
90             ($caller, 'attributes') => sub {
91 3     3   311 my @attr = @_;
92 3         12 while (@attr) {
93 26 100       58 my @names = ref $attr[0] eq 'ARRAY' ? @{ shift @attr } : shift @attr;
  2         6  
94 26         29 my @sp = @{ shift(@attr) };
  26         60  
95             splice @sp, $#sp < 1 ? 0 : 1, 0, delete $sp[-1]->{default}
96 26 100 100     76 if ref $sp[-1] eq 'HASH' && exists $sp[-1]->{default};
    100          
97 26 100 100     176 unshift @sp, 'ro' unless (!$sp[0] || !ref $sp[0]) && ($sp[0] || "") =~ m/^(ro|rw|set)$/;
      100        
      100        
98 26         67 my %params = (is => $sp[0]);
99 25     25   283050 $params{default} = ref $sp[1] eq 'CODE' ? $sp[1] : sub { Object::Proto::clone($sp[1]) }
100 26 100       107 if defined $sp[1];
    100          
101 26 100       60 %params = (%params, %{ $sp[2] }) if ref $sp[2] eq 'HASH';
  7         21  
102 26         1765 push @spec, $_, \%params for @names;
103             }
104             }
105 27         3017 );
106              
107              
108             BEGIN::Lift::install(
109 4     4   373 ($caller, 'extends') => sub { push @extends, @_ }
110 27         2413 );
111              
112             BEGIN::Lift::install(
113 1     1   62 ($caller, 'with') => sub { push @with, @_ }
114 27         2166 );
115              
116             BEGIN::Lift::install(
117 1     1   63 ($caller, 'requires') => sub { push @requires, @_ }
118 27         2246 );
119              
120 27         2097 for my $mod_type (qw/before after around/) {
121             BEGIN::Lift::install(
122             ($caller, $mod_type) => sub {
123 3     3   25 my ($name, $code) = @_;
124 3         741 push @modifiers, [$mod_type, $name, $code];
125             }
126 81         4143 );
127             }
128              
129             Devel::Hook->push_UNITCHECK_hook(sub {
130 27     27   12171 my @spec_copy = @spec;
131 27         52 my (@func_names, $attr, $spec, %isa, @attributes);
132 27         83 while (@spec) {
133 113         174 ($attr, $spec) = (shift @spec, shift @spec);
134 113         209 $attr = _configure_is($attr, $spec);
135 113         211 $attr = _configure_required($attr, $spec);
136 113         160 $attr = _configure_lazy($attr, $spec);
137 113         186 $attr = _configure_isa_and_coerce($attr, $spec, \%isa, $caller);
138 113         286 $attr = _configure_default_and_builder($attr, $spec, \%isa, $caller);
139 113         222 $attr = _configure_trigger($attr, $spec, \%isa, $caller);
140 113         195 $attr = _configure_predicate($attr, $spec, $caller, 'predicate');
141 113         177 $attr = _configure_clearer($attr, $spec, $caller, 'clearer');
142 113         194 $attr = _configure_reader_and_writer($attr, $spec, $caller);
143 113         2109 $attr = _configure_init_arg($attr, $spec, $caller);
144 113         4879 $attr = _configure_weak_ref($attr, $spec, $caller);
145 113         1588 push @attributes, $attr;
146             }
147              
148 27 100       1478 my @extends_arg = @extends > 1
    50          
149             ? (extends => \@extends)
150             : @extends
151             ? (extends => $extends[0])
152             : ();
153 27 100       1533 if ($is_role) {
154 1         11 Object::Proto::role($caller, @attributes);
155 1 50       3 Object::Proto::requires($caller, @requires) if @requires;
156             } else {
157 26         884 Object::Proto::define($caller, @extends_arg, @attributes);
158             }
159 27 100       82 Object::Proto::with($caller, @with) if @with;
160              
161 27         58 while (@spec_copy) {
162 113         1566 my ($name, $spec) = (shift @spec_copy, shift @spec_copy);
163 113         3011 push @func_names, _install_func_accessors($caller, $name, $spec);
164             }
165              
166 27 100       54 if (@func_names) {
167 21     21   362 no strict 'refs';
  21         73  
  21         1223  
168 4         4 push @{"${caller}::EXPORT_FUNC"}, @func_names;
  4         14  
169             }
170              
171             {
172 21     21   100 no strict 'refs';
  21         30  
  21         9384  
  27         32  
173 27         160 *{"${caller}::import_accessors"} = sub {
174 3     3   134523 my ($class, @names) = @_;
175 3         6 my $target = caller();
176             # Use C-level installer - creates CVs with call checkers
177             # so code compiled after this gets custom ops
178 3 100       7 unless (@names) {
179 2         5 for my $pkg (_mro($class)) {
180 4         36 Object::Proto::import_accessors($pkg, $target);
181             }
182             } else {
183 1         3 for my $name (@names) {
184 1         3 for my $pkg (_mro($class)) {
185 1 50       2 if (defined &{"${pkg}::${name}"}) {
  1         5  
186 1         8 Object::Proto::import_accessor($pkg, $name, $name, $target);
187 1         3 last;
188             }
189             }
190             }
191             }
192 27         162 };
193             }
194              
195 27         12799 for my $mod (@modifiers) {
196 3         3 my ($type, $name, $code) = @{$mod};
  3         5  
197 3 50       6 my $meth = $name =~ /::/ ? $name : "${caller}::${name}";
198 3         619 $modifier_dispatch{$type}->($meth, $code);
199             }
200 27         2170 });
201             }
202              
203             sub _mro {
204 3     3   5 my ($class) = @_;
205 3         6 my (@queue, @order, %seen) = ($class);
206 3         8 while (my $pkg = shift @queue) {
207 6 50       11 next if $seen{$pkg}++;
208 6         8 push @order, $pkg;
209 21     21   130 no strict 'refs';
  21         27  
  21         14564  
210 6         7 push @queue, @{"${pkg}::ISA"};
  6         40  
211             }
212 3         9 return @order;
213             }
214              
215             sub _configure_is {
216 113     113   151 my ($attr, $spec) = @_;
217 113 100       203 if (defined $spec->{is}) {
218 108 100       190 if ($spec->{is} eq 'ro') {
219 54         78 $attr .= ":readonly";
220             }
221             }
222 113         164 return $attr;
223             }
224              
225              
226             sub _configure_required {
227 113     113   140 my ($attr, $spec) = @_;
228 113 100       227 if ($spec->{required}) {
229 1         2 $attr .= ":required";
230             }
231 113         149 return $attr;
232             }
233              
234             sub _configure_lazy {
235 113     113   135 my ($attr, $spec) = @_;
236 113 100       158 if ( $spec->{lazy} ) {
237 9         17 $attr .= ":lazy";
238             }
239 113         138 return $attr;
240             }
241              
242             sub _configure_isa_and_coerce {
243 113     113   168 my ($attr, $spec, $isa, $caller) = @_;
244 113         134 my ($ref, $val1, $val2);
245 113 100 100     272 if (defined $spec->{isa} || defined $spec->{coerce}) {
246 49   100     137 $ref = ref $spec->{isa} || "";
247 49 100 100     158 if ($ref eq 'CODE' || defined $spec->{coerce}) {
    50          
248 5 100       9 $val1 = (exists $spec->{isa} ? $spec->{isa} + 0 : '0000');
249 5 100       9 $val2 = (exists $spec->{coerce} ? $spec->{coerce} + 0 : '0000');
250 5 100       17 if (!$isa->{$val1 . $val2}++) {
251             Object::Proto::register_type('T' . $val1 . $val2,
252 4     4   152095 $spec->{isa} || sub { 1 },
253 4     4   133231 $spec->{coerce} || sub { $_[0] }
254 4   66     30 );
      66        
255             }
256 5         10 $attr .= sprintf(":T%s%s", $val1, $val2);
257             } elsif ( !$ref ) {
258 44         72 $val1 = ucfirst($spec->{isa});
259 44 50       88 if ($valid_types{$val1}) {
260 44         70 $attr .= sprintf(":%s", $val1);
261             }
262             } else {
263 0         0 croak "Failed to attach isa for $attr in $caller";
264             }
265             }
266              
267 113         183 return $attr;
268             }
269              
270             sub _configure_default_and_builder {
271 113     113   149 my ($attr, $spec, $isa, $caller) = @_;
272 113         128 my ($ref1, $ref2, $val1, $cb);
273 113 100 100     297 return $attr unless exists $spec->{default} || exists $spec->{builder};
274 74   100     144 $ref1 = ref($spec->{default}) || "";
275 74 100 100     1543 if (exists $spec->{builder} || $ref1 eq 'CODE') {
    100          
    100          
    50          
276 64         77 $ref2 = ref($spec->{builder});
277 64 100 100     217 if (! $ref2 && $ref1 ne 'CODE') {
    50 66        
278 6 50       29 if ($spec->{builder} =~ m/^1$/) {
279 6         13 $attr .= ':builder()';
280             } else {
281 0         0 $attr .= sprintf(":builder(%s)", $spec->{builder});
282             }
283             } elsif ( $ref2 eq 'CODE' || $ref1 eq 'CODE' ) {
284 58 100       1542 my $cb = exists $spec->{builder} ? $spec->{builder} : $spec->{default};
285 58         108 $val1 = 'BUILDER' . ($cb + 0);
286 58 100       1935 if (!$isa->{$val1}++) {
287 21     21   137 no strict 'refs';
  21         30  
  21         6991  
288 50         83 *{"${caller}::${val1}"} = $cb;
  50         167  
289             }
290 58         97 $attr .= sprintf(":builder(%s)", $val1);
291             } else {
292 0         0 croak "Failed to attach builder for $attr in $caller";
293             }
294             } elsif ( ! $ref1 ) {
295 7 100       32 $attr .= sprintf(":default(%s)", defined $spec->{default} ? $spec->{default} : 'undef' );
296             } elsif ( $ref1 eq 'ARRAY') {
297 2         4 $attr .= ":default([])";
298             } elsif ( $ref1 eq 'HASH' ) {
299 1         2 $attr .= ":default({})";
300             }
301 74         143 return $attr;
302             }
303              
304             sub _configure_trigger {
305 113     113   148 my ($attr, $spec, $isa, $caller) = @_;
306 113         123 my ($ref, $val1);
307 113 100       169 if (exists $spec->{trigger}) {
308 2         5 $ref = ref $spec->{trigger};
309 2 50       7 if ( ! $ref ) {
    50          
310 0         0 $attr .= sprintf(":trigger(%s)", $spec->{trigger});
311             } elsif ( $ref eq 'CODE' ) {
312 2         5 $val1 = 'TRIG' . ($spec->{trigger} + 0);
313 2 100       9 if (!$isa->{$val1}++) {
314 21     21   116 no strict 'refs';
  21         30  
  21         20537  
315 1         1 *{"${caller}::${val1}"} = $spec->{trigger};
  1         6  
316             }
317 2         6 $attr .= sprintf(":trigger(%s::%s)", $caller, $val1);
318             }
319              
320             }
321 113         148 return $attr;
322             }
323              
324             sub _configure_predicate {
325 113     113   220 my ($attr, $spec, $caller) = @_;
326 113 100       168 if (defined $spec->{predicate}) {
327 3 100       39 if ($spec->{predicate} =~ 1) {
    50          
328 2         5 $attr .= ":predicate";
329             } elsif (! ref $spec->{predicate}) {
330 1         4 $attr .= sprintf(":predicate(%s)", $spec->{predicate});
331             } else {
332 0         0 croak "Failed to attach predicate for $attr in $caller";
333             }
334             }
335 113         1564 return $attr;
336             }
337              
338             sub _configure_clearer {
339 113     113   162 my ($attr, $spec, $caller) = @_;
340 113 100       190 if (defined $spec->{clearer}) {
341 5 100       19 if ($spec->{clearer} =~ 1) {
    50          
342 4         22 $attr .= ":clearer";
343             } elsif (! ref $spec->{clearer}) {
344 1         2 $attr .= sprintf(":clearer(%s)", $spec->{clearer});
345             } else {
346 0         0 croak "Failed to attach clearer for $attr in $caller";
347             }
348             }
349 113         139 return $attr;
350             }
351              
352             sub _configure_reader_and_writer {
353 113     113   164 my ($attr, $spec, $caller) = @_;
354 113         331 my ($name) = $attr =~ m/^([^\:]+)/;
355 113 100       197 if (exists $spec->{reader}) {
356 3 50       7 croak "Failed to attach reader for $attr in $caller" unless ! ref $spec->{reader};
357 3 100       8 if ($spec->{reader} =~ m/^1$/) {
358 2         1448 $attr .= sprintf(":reader(get_%s)", $name);
359             } else {
360 1         3 $attr .= sprintf(":reader(%s)", $spec->{reader});
361             }
362             }
363 113 100       198 if (exists $spec->{writer}) {
364 3 50       8 croak "Failed to attach writer for $attr in $caller" unless ! ref $spec->{writer};
365 3 100       11 if ($spec->{writer} =~ m/^1$/) {
366 2         5 $attr .= sprintf(":writer(set_%s)", $name);
367             } else {
368 1         2 $attr .= sprintf(":writer(%s)", $spec->{writer});
369             }
370             }
371 113         187 return $attr;
372             }
373              
374             sub _configure_init_arg {
375 113     113   166 my ($attr, $spec) = @_;
376 113 100 100     371 if (defined $spec->{init_arg} || defined $spec->{arg}) {
377 2   66     6 $attr .= sprintf(":arg(%s)", $spec->{init_arg} || $spec->{arg});
378             }
379 113         187 return $attr;
380             }
381              
382             sub _configure_weak_ref {
383 113     113   150 my ($attr, $spec) = @_;
384 113 100 66     290 if ($spec->{weak_ref} || $spec->{weak}) {
385 1         2 $attr .= ':weak';
386             }
387 113         137 return $attr;
388             }
389              
390             sub _install_func_accessors {
391 113     113   1540 my ($caller, $name, $spec) = @_;
392 113         1452 my @installed;
393 113 100       171 if (exists $spec->{accessor}) {
394 5 100       9 my $fname = $spec->{accessor} eq '1' ? $name : $spec->{accessor};
395 5         14 Object::Proto::import_accessor($caller, $name, $fname, $caller);
396 5         6 push @installed, $fname;
397             }
398 113 100 66     1581 if (exists $spec->{reader} && !ref $spec->{reader}) {
399 3 100       8 my $fname = $spec->{reader} eq '1' ? "get_$name" : $spec->{reader};
400 3         9 Object::Proto::import_accessor($caller, $name, $fname, $caller);
401 3         4 push @installed, $fname;
402             }
403 113 100 66     1650 if (exists $spec->{writer} && !ref $spec->{writer}) {
404 3 100       5 my $fname = $spec->{writer} eq '1' ? "set_$name" : $spec->{writer};
405 3         6 Object::Proto::import_accessor($caller, $name, $fname, $caller);
406 3         4 push @installed, $fname;
407             }
408 113         281 return @installed;
409             }
410              
411             1;
412              
413             __END__