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   1734075 use 5.008003;
  21         60  
4 21     21   87 use strict;
  21         33  
  21         585  
5 21     21   119 use warnings;
  21         52  
  21         979  
6 21     21   7338 use BEGIN::Lift;
  21         115285  
  21         562  
7 21     21   112 use Devel::Hook;
  21         32  
  21         441  
8 21     21   8508 use Object::Proto;
  21         9541  
  21         1251  
9 21     21   114 use Carp qw/croak/;
  21         38  
  21         1491  
10              
11             our $VERSION = 0.03;
12              
13 21     21   141 use constant ro => 'ro';
  21         74  
  21         2159  
14 21     21   103 use constant is_ro => ( is => ro );
  21         28  
  21         1086  
15 21     21   106 use constant rw => 'rw';
  21         32  
  21         960  
16 21     21   86 use constant is_rw => ( is => rw );
  21         24  
  21         935  
17 21     21   77 use constant nan => undef;
  21         43  
  21         871  
18 21     21   77 use constant lzy => ( lazy => 1 );
  21         46  
  21         768  
19 21     21   108 use constant bld => ( builder => 1 );
  21         30  
  21         755  
20 21     21   148 use constant lzy_bld => ( lazy_build => 1 );
  21         80  
  21         898  
21 21     21   91 use constant trg => ( trigger => 1 );
  21         35  
  21         905  
22 21     21   71 use constant clr => ( clearer => 1 );
  21         34  
  21         733  
23 21     21   73 use constant req => ( required => 1 );
  21         61  
  21         940  
24 21     21   74 use constant coe => ( coerce => 1 );
  21         32  
  21         951  
25 21     21   75 use constant lzy_hash => (lazy => 1, isa => 'HashRef', default => {} );
  21         26  
  21         940  
26 21     21   123 use constant lzy_array => (lazy => 1, isa => 'ArrayRef', default => [] );
  21         37  
  21         1009  
27 21     21   102 use constant lzy_str => (lazy => 1, isa => 'Str', default => "");
  21         30  
  21         932  
28 21     21   66 use constant dhash => (isa => 'HashRef', default => {});
  21         27  
  21         841  
29 21     21   72 use constant darray => (isa => 'ArrayRef', default => []);
  21         21  
  21         898  
30 21     21   81 use constant dstr => (isa => 'Str', default => "");
  21         42  
  21         3757  
31              
32             our (%valid_types, @type_list, %valid_constants, %modifier_dispatch);
33              
34             BEGIN {
35 21     21   66 @type_list = @{ Object::Proto::list_types() };
  21         336  
36 21         66 %valid_types = map { $_ => 1 } @type_list;
  210         380  
37 21         51 %valid_constants = map { $_ => 1 } qw(
  378         489  
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         1242 %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   200 my ($pkg, @import) = @_;
51 21     21   98 no strict 'refs';
  21         43  
  21         22982  
52 27         91 my $caller = caller();
53 27         40 my (@spec, @modifiers, @extends, @with, @requires, $is_role);
54 27 100       105 $is_role = 1 if grep { $_ eq '-role' } @import;
  8         22  
55              
56 27 50       58 if (grep { $_ eq '-types' } @import) {
  8         13  
57 0     0   0 *{"${caller}::${_}"} = sub { $_ } for @type_list;
  0         0  
  0         0  
58             }
59 27 100       58 if (grep { $_ eq '-constants' } @import) {
  8         12  
60 2         8 *{"${caller}::${_}"} = \&{"Object::Proto::Sugar::${_}"} for keys %valid_constants;
  36         57  
  36         80  
61             }
62              
63 27         50 for my $name (@import) {
64 8 100       19 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       10 unless $valid_constants{$name};
72 5         4 *{"${caller}::${name}"} = \&{"Object::Proto::Sugar::${name}"};
  5         9  
  5         9  
73             }
74             }
75              
76             BEGIN::Lift::install(
77             ($caller, 'has') => sub {
78 77     77   1460 my ($name, %params) = @_;
79 77 100       159 if (ref $name) {
80 3         4 for (@{$name}) {
  3         8  
81 9         829 push @spec, $_, \%params;
82             }
83             } else {
84 74         13546 push @spec, $name, \%params;
85             }
86             }
87 27         180 );
88              
89             BEGIN::Lift::install(
90             ($caller, 'attributes') => sub {
91 3     3   285 my @attr = @_;
92 3         14 while (@attr) {
93 26 100       45 my @names = ref $attr[0] eq 'ARRAY' ? @{ shift @attr } : shift @attr;
  2         3  
94 26         23 my @sp = @{ shift(@attr) };
  26         38  
95             splice @sp, $#sp < 1 ? 0 : 1, 0, delete $sp[-1]->{default}
96 26 100 100     57 if ref $sp[-1] eq 'HASH' && exists $sp[-1]->{default};
    100          
97 26 100 100     123 unshift @sp, 'ro' unless (!$sp[0] || !ref $sp[0]) && ($sp[0] || "") =~ m/^(ro|rw|set)$/;
      100        
      100        
98 26         42 my %params = (is => $sp[0]);
99 25     25   269984 $params{default} = ref $sp[1] eq 'CODE' ? $sp[1] : sub { Object::Proto::clone($sp[1]) }
100 26 100       59 if defined $sp[1];
    100          
101 26 100       36 %params = (%params, %{ $sp[2] }) if ref $sp[2] eq 'HASH';
  7         16  
102 26         1153 push @spec, $_, \%params for @names;
103             }
104             }
105 27         2768 );
106              
107              
108             BEGIN::Lift::install(
109 4     4   281 ($caller, 'extends') => sub { push @extends, @_ }
110 27         2164 );
111              
112             BEGIN::Lift::install(
113 1     1   54 ($caller, 'with') => sub { push @with, @_ }
114 27         1962 );
115              
116             BEGIN::Lift::install(
117 1     1   75 ($caller, 'requires') => sub { push @requires, @_ }
118 27         2126 );
119              
120 27         1870 for my $mod_type (qw/before after around/) {
121             BEGIN::Lift::install(
122             ($caller, $mod_type) => sub {
123 3     3   22 my ($name, $code) = @_;
124 3         712 push @modifiers, [$mod_type, $name, $code];
125             }
126 81         3662 );
127             }
128              
129             Devel::Hook->push_UNITCHECK_hook(sub {
130 27     27   11610 my @spec_copy = @spec;
131 27         71 my (@func_names, $attr, $spec, %isa, @attributes);
132 27         80 while (@spec) {
133 113         189 ($attr, $spec) = (shift @spec, shift @spec);
134 113         194 $attr = _configure_is($attr, $spec);
135 113         210 $attr = _configure_required($attr, $spec);
136 113         154 $attr = _configure_lazy($attr, $spec);
137 113         163 $attr = _configure_isa_and_coerce($attr, $spec, \%isa, $caller);
138 113         268 $attr = _configure_default_and_builder($attr, $spec, \%isa, $caller);
139 113         208 $attr = _configure_trigger($attr, $spec, \%isa, $caller);
140 113         1513 $attr = _configure_predicate($attr, $spec, $caller, 'predicate');
141 113         198 $attr = _configure_clearer($attr, $spec, $caller, 'clearer');
142 113         182 $attr = _configure_reader_and_writer($attr, $spec, $caller);
143 113         189 $attr = _configure_init_arg($attr, $spec, $caller);
144 113         4141 $attr = _configure_weak_ref($attr, $spec, $caller);
145 113         232 push @attributes, $attr;
146             }
147              
148 27 100       2935 my @extends_arg = @extends > 1
    50          
149             ? (extends => \@extends)
150             : @extends
151             ? (extends => $extends[0])
152             : ();
153 27 100       64 if ($is_role) {
154 1         8 Object::Proto::role($caller, @attributes);
155 1 50       4 Object::Proto::requires($caller, @requires) if @requires;
156             } else {
157 26         842 Object::Proto::define($caller, @extends_arg, @attributes);
158             }
159 27 100       71 Object::Proto::with($caller, @with) if @with;
160              
161 27         1630 while (@spec_copy) {
162 113         3025 my ($name, $spec) = (shift @spec_copy, shift @spec_copy);
163 113         163 push @func_names, _install_func_accessors($caller, $name, $spec);
164             }
165              
166 27 100       48 if (@func_names) {
167 21     21   240 no strict 'refs';
  21         106  
  21         1214  
168 4         4 push @{"${caller}::EXPORT_FUNC"}, @func_names;
  4         13  
169             }
170              
171             {
172 21     21   81 no strict 'refs';
  21         55  
  21         8850  
  27         34  
173 27         162 *{"${caller}::import_accessors"} = sub {
174 3     3   137428 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       8 unless (@names) {
179 2         5 for my $pkg (_mro($class)) {
180 4         26 Object::Proto::import_accessors($pkg, $target);
181             }
182             } else {
183 1         2 for my $name (@names) {
184 1         4 for my $pkg (_mro($class)) {
185 1 50       2 if (defined &{"${pkg}::${name}"}) {
  1         5  
186 1         9 Object::Proto::import_accessor($pkg, $name, $name, $target);
187 1         3 last;
188             }
189             }
190             }
191             }
192 27         120 };
193             }
194              
195 27         11404 for my $mod (@modifiers) {
196 3         3 my ($type, $name, $code) = @{$mod};
  3         4  
197 3 50       7 my $meth = $name =~ /::/ ? $name : "${caller}::${name}";
198 3         637 $modifier_dispatch{$type}->($meth, $code);
199             }
200 27         2025 });
201             }
202              
203             sub _mro {
204 3     3   4 my ($class) = @_;
205 3         6 my (@queue, @order, %seen) = ($class);
206 3         7 while (my $pkg = shift @queue) {
207 6 50       14 next if $seen{$pkg}++;
208 6         25 push @order, $pkg;
209 21     21   128 no strict 'refs';
  21         45  
  21         13137  
210 6         8 push @queue, @{"${pkg}::ISA"};
  6         23  
211             }
212 3         8 return @order;
213             }
214              
215             sub _configure_is {
216 113     113   143 my ($attr, $spec) = @_;
217 113 100       200 if (defined $spec->{is}) {
218 108 100       204 if ($spec->{is} eq 'ro') {
219 54         69 $attr .= ":readonly";
220             }
221             }
222 113         157 return $attr;
223             }
224              
225              
226             sub _configure_required {
227 113     113   143 my ($attr, $spec) = @_;
228 113 100       172 if ($spec->{required}) {
229 1         2 $attr .= ":required";
230             }
231 113         200 return $attr;
232             }
233              
234             sub _configure_lazy {
235 113     113   132 my ($attr, $spec) = @_;
236 113 100       161 if ( $spec->{lazy} ) {
237 9         13 $attr .= ":lazy";
238             }
239 113         174 return $attr;
240             }
241              
242             sub _configure_isa_and_coerce {
243 113     113   162 my ($attr, $spec, $isa, $caller) = @_;
244 113         113 my ($ref, $val1, $val2);
245 113 100 100     265 if (defined $spec->{isa} || defined $spec->{coerce}) {
246 49   100     132 $ref = ref $spec->{isa} || "";
247 49 100 100     147 if ($ref eq 'CODE' || defined $spec->{coerce}) {
    50          
248 5 100       9 $val1 = (exists $spec->{isa} ? $spec->{isa} + 0 : '0000');
249 5 100       8 $val2 = (exists $spec->{coerce} ? $spec->{coerce} + 0 : '0000');
250 5 100       18 if (!$isa->{$val1 . $val2}++) {
251             Object::Proto::register_type('T' . $val1 . $val2,
252 4     4   130625 $spec->{isa} || sub { 1 },
253 4     4   141694 $spec->{coerce} || sub { $_[0] }
254 4   66     49 );
      66        
255             }
256 5         8 $attr .= sprintf(":T%s%s", $val1, $val2);
257             } elsif ( !$ref ) {
258 44         69 $val1 = ucfirst($spec->{isa});
259 44 50       82 if ($valid_types{$val1}) {
260 44         94 $attr .= sprintf(":%s", $val1);
261             }
262             } else {
263 0         0 croak "Failed to attach isa for $attr in $caller";
264             }
265             }
266              
267 113         164 return $attr;
268             }
269              
270             sub _configure_default_and_builder {
271 113     113   156 my ($attr, $spec, $isa, $caller) = @_;
272 113         114 my ($ref1, $ref2, $val1, $cb);
273 113 100 100     253 return $attr unless exists $spec->{default} || exists $spec->{builder};
274 74   100     130 $ref1 = ref($spec->{default}) || "";
275 74 100 100     1477 if (exists $spec->{builder} || $ref1 eq 'CODE') {
    100          
    100          
    50          
276 64         74 $ref2 = ref($spec->{builder});
277 64 100 100     1615 if (! $ref2 && $ref1 ne 'CODE') {
    50 66        
278 6 50       25 if ($spec->{builder} =~ m/^1$/) {
279 6         10 $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       82 my $cb = exists $spec->{builder} ? $spec->{builder} : $spec->{default};
285 58         1511 $val1 = 'BUILDER' . ($cb + 0);
286 58 100       130 if (!$isa->{$val1}++) {
287 21     21   139 no strict 'refs';
  21         27  
  21         6487  
288 50         82 *{"${caller}::${val1}"} = $cb;
  50         147  
289             }
290 58         94 $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         3 $attr .= ":default([])";
298             } elsif ( $ref1 eq 'HASH' ) {
299 1         2 $attr .= ":default({})";
300             }
301 74         115 return $attr;
302             }
303              
304             sub _configure_trigger {
305 113     113   164 my ($attr, $spec, $isa, $caller) = @_;
306 113         115 my ($ref, $val1);
307 113 100       204 if (exists $spec->{trigger}) {
308 2         4 $ref = ref $spec->{trigger};
309 2 50       5 if ( ! $ref ) {
    50          
310 0         0 $attr .= sprintf(":trigger(%s)", $spec->{trigger});
311             } elsif ( $ref eq 'CODE' ) {
312 2         4 $val1 = 'TRIG' . ($spec->{trigger} + 0);
313 2 100       6 if (!$isa->{$val1}++) {
314 21     21   109 no strict 'refs';
  21         26  
  21         18198  
315 1         1 *{"${caller}::${val1}"} = $spec->{trigger};
  1         5  
316             }
317 2         4 $attr .= sprintf(":trigger(%s::%s)", $caller, $val1);
318             }
319              
320             }
321 113         138 return $attr;
322             }
323              
324             sub _configure_predicate {
325 113     113   150 my ($attr, $spec, $caller) = @_;
326 113 100       214 if (defined $spec->{predicate}) {
327 3 100       11 if ($spec->{predicate} =~ 1) {
    50          
328 2         3 $attr .= ":predicate";
329             } elsif (! ref $spec->{predicate}) {
330 1         1 $attr .= sprintf(":predicate(%s)", $spec->{predicate});
331             } else {
332 0         0 croak "Failed to attach predicate for $attr in $caller";
333             }
334             }
335 113         135 return $attr;
336             }
337              
338             sub _configure_clearer {
339 113     113   149 my ($attr, $spec, $caller) = @_;
340 113 100       156 if (defined $spec->{clearer}) {
341 5 100       14 if ($spec->{clearer} =~ 1) {
    50          
342 4         5 $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         131 return $attr;
350             }
351              
352             sub _configure_reader_and_writer {
353 113     113   139 my ($attr, $spec, $caller) = @_;
354 113         309 my ($name) = $attr =~ m/^([^\:]+)/;
355 113 100       175 if (exists $spec->{reader}) {
356 3 50       6 croak "Failed to attach reader for $attr in $caller" unless ! ref $spec->{reader};
357 3 100       11 if ($spec->{reader} =~ m/^1$/) {
358 2         1412 $attr .= sprintf(":reader(get_%s)", $name);
359             } else {
360 1         2 $attr .= sprintf(":reader(%s)", $spec->{reader});
361             }
362             }
363 113 100       186 if (exists $spec->{writer}) {
364 3 50       8 croak "Failed to attach writer for $attr in $caller" unless ! ref $spec->{writer};
365 3 100       9 if ($spec->{writer} =~ m/^1$/) {
366 2         3 $attr .= sprintf(":writer(set_%s)", $name);
367             } else {
368 1         1 $attr .= sprintf(":writer(%s)", $spec->{writer});
369             }
370             }
371 113         1587 return $attr;
372             }
373              
374             sub _configure_init_arg {
375 113     113   145 my ($attr, $spec) = @_;
376 113 100 100     322 if (defined $spec->{init_arg} || defined $spec->{arg}) {
377 2   66     8 $attr .= sprintf(":arg(%s)", $spec->{init_arg} || $spec->{arg});
378             }
379 113         143 return $attr;
380             }
381              
382             sub _configure_weak_ref {
383 113     113   157 my ($attr, $spec) = @_;
384 113 100 66     268 if ($spec->{weak_ref} || $spec->{weak}) {
385 1         2 $attr .= ':weak';
386             }
387 113         1498 return $attr;
388             }
389              
390             sub _install_func_accessors {
391 113     113   154 my ($caller, $name, $spec) = @_;
392 113         2029 my @installed;
393 113 100       220 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         5 push @installed, $fname;
397             }
398 113 100 66     2958 if (exists $spec->{reader} && !ref $spec->{reader}) {
399 3 100       6 my $fname = $spec->{reader} eq '1' ? "get_$name" : $spec->{reader};
400 3         24 Object::Proto::import_accessor($caller, $name, $fname, $caller);
401 3         4 push @installed, $fname;
402             }
403 113 100 66     165 if (exists $spec->{writer} && !ref $spec->{writer}) {
404 3 100       7 my $fname = $spec->{writer} eq '1' ? "set_$name" : $spec->{writer};
405 3         7 Object::Proto::import_accessor($caller, $name, $fname, $caller);
406 3         3 push @installed, $fname;
407             }
408 113         1667 return @installed;
409             }
410              
411             1;
412              
413             __END__