File Coverage

blib/lib/Object/Proto/Sugar.pm
Criterion Covered Total %
statement 332 345 96.2
branch 131 152 86.1
condition 57 68 83.8
subroutine 61 63 96.8
pod n/a
total 581 628 92.5


line stmt bran cond sub pod time code
1             package Object::Proto::Sugar;
2              
3 22     22   1799096 use 5.008003;
  22         67  
4 22     22   101 use strict;
  22         33  
  22         584  
5 22     22   87 use warnings;
  22         51  
  22         983  
6 22     22   7920 use BEGIN::Lift;
  22         119651  
  22         626  
7 22     22   114 use Devel::Hook;
  22         31  
  22         439  
8 22     22   9776 use Object::Proto;
  22         10103  
  22         1354  
9 22     22   138 use Carp qw/croak/;
  22         38  
  22         1588  
10              
11             our $VERSION = 0.05;
12              
13 22     22   151 use constant ro => 'ro';
  22         58  
  22         2287  
14 22     22   127 use constant is_ro => ( is => ro );
  22         28  
  22         1149  
15 22     22   123 use constant rw => 'rw';
  22         32  
  22         1098  
16 22     22   89 use constant is_rw => ( is => rw );
  22         37  
  22         987  
17 22     22   76 use constant nan => undef;
  22         34  
  22         864  
18 22     22   95 use constant lzy => ( lazy => 1 );
  22         26  
  22         860  
19 22     22   78 use constant bld => ( builder => 1 );
  22         27  
  22         829  
20 22     22   213 use constant lzy_bld => ( lazy_build => 1 );
  22         44  
  22         1069  
21 22     22   113 use constant trg => ( trigger => 1 );
  22         40  
  22         954  
22 22     22   74 use constant clr => ( clearer => 1 );
  22         26  
  22         832  
23 22     22   79 use constant req => ( required => 1 );
  22         26  
  22         963  
24 22     22   77 use constant coe => ( coerce => 1 );
  22         28  
  22         996  
25 22     22   86 use constant lzy_hash => (lazy => 1, isa => 'HashRef', default => {} );
  22         26  
  22         992  
26 22     22   76 use constant lzy_array => (lazy => 1, isa => 'ArrayRef', default => [] );
  22         45  
  22         1126  
27 22     22   112 use constant lzy_str => (lazy => 1, isa => 'Str', default => "");
  22         36  
  22         995  
28 22     22   112 use constant dhash => (isa => 'HashRef', default => {});
  22         33  
  22         960  
29 22     22   81 use constant darray => (isa => 'ArrayRef', default => []);
  22         23  
  22         1002  
30 22     22   94 use constant dstr => (isa => 'Str', default => "");
  22         35  
  22         4167  
31              
32             our (%valid_types, @type_list, %valid_constants, %modifier_dispatch, %accessor_aliases);
33              
34             BEGIN {
35 22     22   66 @type_list = @{ Object::Proto::list_types() };
  22         294  
36 22         59 %valid_types = map { $_ => 1 } @type_list;
  220         402  
37 22         78 %valid_constants = map { $_ => 1 } qw(
  396         502  
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 22         2756 %modifier_dispatch = (
43             before => \&Object::Proto::before,
44             after => \&Object::Proto::after,
45             around => \&Object::Proto::around,
46             );
47             }
48              
49             sub import {
50 38     38   332 my ($pkg, @import) = @_;
51 38         86 my $caller = caller();
52 38         76 my (@spec, @modifiers, @extends, @with, @requires, $is_role, $accessor_alias);
53 38 100       122 $is_role = 1 if grep { $_ eq '-role' } @import;
  8         24  
54              
55 38 50       72 if (grep { $_ eq '-types' } @import) {
  8         14  
56 22     22   111 no strict 'refs';
  22         33  
  22         2124  
57 0     0   0 *{"${caller}::${_}"} = sub { $_ } for @type_list;
  0         0  
  0         0  
58             }
59 38 100       64 if (grep { $_ eq '-constants' } @import) {
  8         21  
60 22     22   320 no strict 'refs';
  22         69  
  22         2222  
61 2         8 *{"${caller}::${_}"} = \&{"Object::Proto::Sugar::${_}"} for keys %valid_constants;
  36         72  
  36         67  
62             }
63              
64 38         68 for my $name (@import) {
65 22     22   122 no strict 'refs';
  22         26  
  22         21752  
66 8 100       18 next if $name =~ /^-/;
67 5 50       9 if ($name =~ /^[A-Z]/) {
68             croak "Unknown type '$name' requested from Object::Proto::Sugar"
69 0 0       0 unless $valid_types{$name};
70 0     0   0 *{"${caller}::${name}"} = sub { $name };
  0         0  
  0         0  
71             } else {
72             croak "Unknown constant '$name' requested from Object::Proto::Sugar"
73 5 50       8 unless $valid_constants{$name};
74 5         5 *{"${caller}::${name}"} = \&{"Object::Proto::Sugar::${name}"};
  5         23  
  5         9  
75             }
76             }
77              
78             BEGIN::Lift::install(
79             ($caller, 'has') => sub {
80 93     93   1824 my ($name, %params) = @_;
81 93 100       205 if (ref $name) {
82 3         4 for (@{$name}) {
  3         13  
83 9         844 push @spec, $_, \%params;
84             }
85             } else {
86 90         15095 push @spec, $name, \%params;
87             }
88             }
89 38         212 );
90              
91             BEGIN::Lift::install(
92             ($caller, 'attributes') => sub {
93 3     3   354 my @attr = @_;
94 3         10 while (@attr) {
95 26 100       43 my @names = ref $attr[0] eq 'ARRAY' ? @{ shift @attr } : shift @attr;
  2         4  
96 26         21 my @sp = @{ shift(@attr) };
  26         35  
97             splice @sp, $#sp < 1 ? 0 : 1, 0, delete $sp[-1]->{default}
98 26 100 100     58 if ref $sp[-1] eq 'HASH' && exists $sp[-1]->{default};
    100          
99 26 100 100     126 unshift @sp, 'ro' unless (!$sp[0] || !ref $sp[0]) && ($sp[0] || "") =~ m/^(ro|rw|set)$/;
      100        
      100        
100 26         71 my %params = (is => $sp[0]);
101 25     25   274696 $params{default} = ref $sp[1] eq 'CODE' ? $sp[1] : sub { Object::Proto::clone($sp[1]) }
102 26 100       72 if defined $sp[1];
    100          
103 26 100       41 %params = (%params, %{ $sp[2] }) if ref $sp[2] eq 'HASH';
  7         38  
104 26         1621 push @spec, $_, \%params for @names;
105             }
106             }
107 38         4031 );
108              
109              
110             BEGIN::Lift::install(
111 7     7   446 ($caller, 'extends') => sub { push @extends, @_ }
112 38         3050 );
113              
114             BEGIN::Lift::install(
115 1     1   49 ($caller, 'with') => sub { push @with, @_ }
116 38         2891 );
117              
118             BEGIN::Lift::install(
119 1     1   68 ($caller, 'requires') => sub { push @requires, @_ }
120 38         2607 );
121              
122             BEGIN::Lift::install(
123 9     9   420 ($caller, 'accessor_alias') => sub { $accessor_alias = $_[0] }
124 38         2791 );
125              
126 38         2651 for my $mod_type (qw/before after around/) {
127             BEGIN::Lift::install(
128             ($caller, $mod_type) => sub {
129 3     3   35 my ($name, $code) = @_;
130 3         748 push @modifiers, [$mod_type, $name, $code];
131             }
132 114         7315 );
133             }
134              
135             Devel::Hook->push_UNITCHECK_hook(sub {
136 38     38   12211 my @spec_copy = @spec;
137 38         63 my (@func_names, $attr, $spec, %isa, @attributes);
138 38         120 while (@spec) {
139 129         215 ($attr, $spec) = (shift @spec, shift @spec);
140 129         429 $attr = _configure_is($attr, $spec);
141 129         220 $attr = _configure_required($attr, $spec);
142 129         214 $attr = _configure_lazy($attr, $spec);
143 129         265 $attr = _configure_isa_and_coerce($attr, $spec, \%isa, $caller);
144 129         229 $attr = _configure_default_and_builder($attr, $spec, \%isa, $caller);
145 129         233 $attr = _configure_trigger($attr, $spec, \%isa, $caller);
146 129         258 $attr = _configure_predicate($attr, $spec, $caller, 'predicate');
147 129         234 $attr = _configure_clearer($attr, $spec, $caller, 'clearer');
148 129         201 $attr = _configure_reader_and_writer($attr, $spec, $caller);
149 129         1619 $attr = _configure_init_arg($attr, $spec, $caller);
150 129         197 $attr = _configure_weak_ref($attr, $spec, $caller);
151 129         288 push @attributes, $attr;
152             }
153              
154 38 100       2955 my @extends_arg = @extends > 1
    50          
155             ? (extends => \@extends)
156             : @extends
157             ? (extends => $extends[0])
158             : ();
159 38 100       66 if ($is_role) {
160 1         7 Object::Proto::role($caller, @attributes);
161 1 50       3 Object::Proto::requires($caller, @requires) if @requires;
162             } else {
163 37         2325 Object::Proto::define($caller, @extends_arg, @attributes);
164             }
165 38 100       1450 Object::Proto::with($caller, @with) if @with;
166              
167 38 100       1428 $accessor_aliases{$caller} = $accessor_alias if $accessor_alias;
168              
169 38         49 my %func_to_attr;
170 38         68 while (@spec_copy) {
171 129         187 my ($name, $spec) = (shift @spec_copy, shift @spec_copy);
172 129         191 my @fnames = _install_func_accessors($caller, $name, $spec, $accessor_alias);
173 129         180 $func_to_attr{$_} = $name for @fnames;
174 129         254 push @func_names, @fnames;
175             }
176              
177 38 100       69 if (@func_names) {
178 22     22   127 no strict 'refs';
  22         30  
  22         1228  
179 15         16 push @{"${caller}::EXPORT_FUNC"}, @func_names;
  15         39  
180             }
181              
182             {
183 22     22   96 no strict 'refs';
  22         39  
  22         684  
  38         43  
184 22     22   90 no warnings 'redefine';
  22         50  
  22         11062  
185 38         145 *{"${caller}::import_accessors"} = sub {
186 7     7   279065 my ($class, @names) = @_;
187 7         14 my $target = caller();
188             # Use C-level installer - creates CVs with call checkers
189             # so code compiled after this gets custom ops
190 7 100       19 unless (@names) {
191 4         13 for my $pkg (_mro($class)) {
192 7   100     20 my $alias = $accessor_aliases{$pkg} || '';
193 7 100       77 Object::Proto::import_accessors($pkg, ($alias ? "${alias}_" : ""), $target);
194             }
195             } else {
196 3         9 for my $name (@names) {
197 3   33     11 my $attr = $func_to_attr{$name} || $name;
198 3         8 for my $pkg (_mro($class)) {
199 3 50       4 if (defined &{"${pkg}::${name}"}) {
  3         11  
200 3         26 Object::Proto::import_accessor($pkg, $attr, $name, $target);
201 3         9 last;
202             }
203             }
204             }
205             }
206 38         151 };
207             }
208              
209 38         12488 for my $mod (@modifiers) {
210 3         3 my ($type, $name, $code) = @{$mod};
  3         4  
211 3 50       8 my $meth = $name =~ /::/ ? $name : "${caller}::${name}";
212 3         583 $modifier_dispatch{$type}->($meth, $code);
213             }
214 38         2902 });
215             }
216              
217             sub _mro {
218 7     7   13 my ($class) = @_;
219 7         14 my (@queue, @order, %seen) = ($class);
220 7         17 while (my $pkg = shift @queue) {
221 12 50       27 next if $seen{$pkg}++;
222 12         16 push @order, $pkg;
223 22     22   119 no strict 'refs';
  22         29  
  22         14548  
224 12         12 push @queue, @{"${pkg}::ISA"};
  12         51  
225             }
226 7         20 return @order;
227             }
228              
229             sub _configure_is {
230 129     129   184 my ($attr, $spec) = @_;
231 129 100       226 if (defined $spec->{is}) {
232 124 100       271 if ($spec->{is} eq 'ro') {
233 54         87 $attr .= ":readonly";
234             }
235             }
236 129         190 return $attr;
237             }
238              
239              
240             sub _configure_required {
241 129     129   180 my ($attr, $spec) = @_;
242 129 100       218 if ($spec->{required}) {
243 1         2 $attr .= ":required";
244             }
245 129         186 return $attr;
246             }
247              
248             sub _configure_lazy {
249 129     129   161 my ($attr, $spec) = @_;
250 129 100       223 if ( $spec->{lazy} ) {
251 9         16 $attr .= ":lazy";
252             }
253 129         171 return $attr;
254             }
255              
256             sub _configure_isa_and_coerce {
257 129     129   199 my ($attr, $spec, $isa, $caller) = @_;
258 129         144 my ($ref, $val1, $val2);
259 129 100 100     331 if (defined $spec->{isa} || defined $spec->{coerce}) {
260 49   100     131 $ref = ref $spec->{isa} || "";
261 49 100 100     177 if ($ref eq 'CODE' || defined $spec->{coerce}) {
    50          
262 5 100       8 $val1 = (exists $spec->{isa} ? $spec->{isa} + 0 : '0000');
263 5 100       11 $val2 = (exists $spec->{coerce} ? $spec->{coerce} + 0 : '0000');
264 5 100       16 if (!$isa->{$val1 . $val2}++) {
265             Object::Proto::register_type('T' . $val1 . $val2,
266 4     4   133919 $spec->{isa} || sub { 1 },
267 4     4   173405 $spec->{coerce} || sub { $_[0] }
268 4   66     1341 );
      66        
269             }
270 5         13 $attr .= sprintf(":T%s%s", $val1, $val2);
271             } elsif ( !$ref ) {
272 44         68 $val1 = ucfirst($spec->{isa});
273 44 50       84 if ($valid_types{$val1}) {
274 44         68 $attr .= sprintf(":%s", $val1);
275             }
276             } else {
277 0         0 croak "Failed to attach isa for $attr in $caller";
278             }
279             }
280              
281 129         203 return $attr;
282             }
283              
284             sub _configure_default_and_builder {
285 129     129   178 my ($attr, $spec, $isa, $caller) = @_;
286 129         149 my ($ref1, $ref2, $val1, $cb);
287 129 100 100     2237 return $attr unless exists $spec->{default} || exists $spec->{builder};
288 74   100     169 $ref1 = ref($spec->{default}) || "";
289 74 100 100     218 if (exists $spec->{builder} || $ref1 eq 'CODE') {
    100          
    100          
    50          
290 64         93 $ref2 = ref($spec->{builder});
291 64 100 100     242 if (! $ref2 && $ref1 ne 'CODE') {
    50 66        
292 6 50       27 if ($spec->{builder} =~ m/^1$/) {
293 6         11 $attr .= ':builder()';
294             } else {
295 0         0 $attr .= sprintf(":builder(%s)", $spec->{builder});
296             }
297             } elsif ( $ref2 eq 'CODE' || $ref1 eq 'CODE' ) {
298 58 100       111 my $cb = exists $spec->{builder} ? $spec->{builder} : $spec->{default};
299 58         123 $val1 = 'BUILDER' . ($cb + 0);
300 58 100       1616 if (!$isa->{$val1}++) {
301 22     22   148 no strict 'refs';
  22         43  
  22         6387  
302 50         86 *{"${caller}::${val1}"} = $cb;
  50         222  
303             }
304 58         129 $attr .= sprintf(":builder(%s)", $val1);
305             } else {
306 0         0 croak "Failed to attach builder for $attr in $caller";
307             }
308             } elsif ( ! $ref1 ) {
309 7 100       1409 $attr .= sprintf(":default(%s)", defined $spec->{default} ? $spec->{default} : 'undef' );
310             } elsif ( $ref1 eq 'ARRAY') {
311 2         3 $attr .= ":default([])";
312             } elsif ( $ref1 eq 'HASH' ) {
313 1         1 $attr .= ":default({})";
314             }
315 74         164 return $attr;
316             }
317              
318             sub _configure_trigger {
319 129     129   206 my ($attr, $spec, $isa, $caller) = @_;
320 129         160 my ($ref, $val1);
321 129 100       218 if (exists $spec->{trigger}) {
322 2         4 $ref = ref $spec->{trigger};
323 2 50       6 if ( ! $ref ) {
    50          
324 0         0 $attr .= sprintf(":trigger(%s)", $spec->{trigger});
325             } elsif ( $ref eq 'CODE' ) {
326 2         4 $val1 = 'TRIG' . ($spec->{trigger} + 0);
327 2 100       6 if (!$isa->{$val1}++) {
328 22     22   120 no strict 'refs';
  22         30  
  22         21064  
329 1         1 *{"${caller}::${val1}"} = $spec->{trigger};
  1         5  
330             }
331 2         3 $attr .= sprintf(":trigger(%s::%s)", $caller, $val1);
332             }
333              
334             }
335 129         190 return $attr;
336             }
337              
338             sub _configure_predicate {
339 129     129   177 my ($attr, $spec, $caller) = @_;
340 129 100       243 if (defined $spec->{predicate}) {
341 3 100       9 if ($spec->{predicate} =~ 1) {
    50          
342 2         3 $attr .= ":predicate";
343             } elsif (! ref $spec->{predicate}) {
344 1         1 $attr .= sprintf(":predicate(%s)", $spec->{predicate});
345             } else {
346 0         0 croak "Failed to attach predicate for $attr in $caller";
347             }
348             }
349 129         190 return $attr;
350             }
351              
352             sub _configure_clearer {
353 129     129   184 my ($attr, $spec, $caller) = @_;
354 129 100       249 if (defined $spec->{clearer}) {
355 5 100       16 if ($spec->{clearer} =~ 1) {
    50          
356 4         8 $attr .= ":clearer";
357             } elsif (! ref $spec->{clearer}) {
358 1         1 $attr .= sprintf(":clearer(%s)", $spec->{clearer});
359             } else {
360 0         0 croak "Failed to attach clearer for $attr in $caller";
361             }
362             }
363 129         200 return $attr;
364             }
365              
366             sub _configure_reader_and_writer {
367 129     129   190 my ($attr, $spec, $caller) = @_;
368 129         3143 my ($name) = $attr =~ m/^([^\:]+)/;
369 129 100       1818 if (exists $spec->{reader}) {
370 4 50       12 croak "Failed to attach reader for $attr in $caller" unless ! ref $spec->{reader};
371 4 100       24 if ($spec->{reader} =~ m/^1$/) {
372 3         8 $attr .= sprintf(":reader(get_%s)", $name);
373             } else {
374 1         2 $attr .= sprintf(":reader(%s)", $spec->{reader});
375             }
376             }
377 129 100       192 if (exists $spec->{writer}) {
378 4 50       10 croak "Failed to attach writer for $attr in $caller" unless ! ref $spec->{writer};
379 4 100       10 if ($spec->{writer} =~ m/^1$/) {
380 3         5 $attr .= sprintf(":writer(set_%s)", $name);
381             } else {
382 1         2 $attr .= sprintf(":writer(%s)", $spec->{writer});
383             }
384             }
385 129         1600 return $attr;
386             }
387              
388             sub _configure_init_arg {
389 129     129   193 my ($attr, $spec) = @_;
390 129 100 100     4539 if (defined $spec->{init_arg} || defined $spec->{arg}) {
391 2   66     7 $attr .= sprintf(":arg(%s)", $spec->{init_arg} || $spec->{arg});
392             }
393 129         195 return $attr;
394             }
395              
396             sub _configure_weak_ref {
397 129     129   168 my ($attr, $spec) = @_;
398 129 100 66     3094 if ($spec->{weak_ref} || $spec->{weak}) {
399 1         2 $attr .= ':weak';
400             }
401 129         171 return $attr;
402             }
403              
404             sub _install_func_accessors {
405 129     129   176 my ($caller, $name, $spec, $alias) = @_;
406 129         155 my @installed;
407 129 100       190 if (exists $spec->{accessor}) {
408             my $fname = ($alias && $spec->{accessor} eq '1')
409             ? $alias . '_' . $name
410 21 100 100     58 : $spec->{accessor} eq '1' ? $name : $spec->{accessor};
    100          
411 21         100 Object::Proto::import_accessor($caller, $name, $fname, $caller);
412 21         27 push @installed, $fname;
413             }
414 129 100 66     220 if (exists $spec->{reader} && !ref $spec->{reader}) {
415 4 100       13 my $fname = $spec->{reader} eq '1' ? "get_$name" : $spec->{reader};
416 4 100 66     12 $fname = $alias . '_' . $fname if $alias && $spec->{reader} eq '1';
417 4         34 Object::Proto::import_accessor($caller, $name, $fname, $caller);
418 4         9 push @installed, $fname;
419             }
420 129 100 66     210 if (exists $spec->{writer} && !ref $spec->{writer}) {
421 4 100       12 my $fname = $spec->{writer} eq '1' ? "set_$name" : $spec->{writer};
422 4 100 66     10 $fname = $alias . '_' . $fname if $alias && $spec->{writer} eq '1';
423 4         24 Object::Proto::import_accessor($caller, $name, $fname, $caller);
424 4         9 push @installed, $fname;
425             }
426 129         206 return @installed;
427             }
428              
429             1;
430              
431             __END__