File Coverage

blib/lib/YAOO.pm
Criterion Covered Total %
statement 206 253 81.4
branch 73 116 62.9
condition 27 49 55.1
subroutine 40 54 74.0
pod 12 32 37.5
total 358 504 71.0


line stmt bran cond sub pod time code
1             package YAOO;
2 6     6   365586 use strict; no strict 'refs';
  6     6   46  
  6         151  
  6         25  
  6         8  
  6         136  
3 6     6   24 use warnings;
  6         8  
  6         197  
4 6     6   28 use Carp qw/croak/; use Tie::IxHash;
  6     6   8  
  6         269  
  6         2602  
  6         23138  
  6         181  
5 6     6   34 use feature qw/state/;
  6         15  
  6         656  
6 6     6   2508 use Blessed::Merge;
  6         104540  
  6         22625  
7             our $VERSION = '0.08';
8              
9             our (%TYPES, %object, $LAST);
10              
11             sub make_keyword {
12 308     308 0 453 my ($called, $key, $cb) = @_;
13 308         286 *{"${called}::$key"} = $cb;
  308         870  
14 308         6285 $LAST = 10000000000000000000;
15             }
16              
17             sub import {
18 9     9   775 my ($package, @attributes) = @_;
19              
20 9         22 my $called = caller();
21              
22 9         63 strict->import();
23 9         110 warnings->import();
24              
25 9         20 for my $is (qw/ro rw/) {
26 18     23   68 make_keyword($called, $is, sub { is => $is });
  23         103  
27             }
28              
29 9         18 for my $key (qw/isa default coerce required trigger lazy delay build_order/) {
30             make_keyword($called, $key, sub {
31 31     31   51 my (@value) = @_;
32 31 100 100     139 return $key => scalar @value > 1 ? @value : ($value[0] || 1);
33 72         169 });
34             }
35              
36 9         16 for my $isa ( qw/any string scalarref integer float boolean ordered_hash hash array object fh/ ) {
37             make_keyword($called, $isa, sub {
38 27     27   55 my (@args) = @_;
39             my @return = (
40 27         76 \&{"${package}::${isa}"},
41             type => $isa,
42 27         31 build_default => \&{"${package}::build_${isa}"}
  27         84  
43             );
44 27 100   6   114 push @return, (default => ($isa eq 'ordered_hash' ? sub { deep_clone_ordered_hash(@args) } : sub { deep_clone( scalar @args > 1 ? $isa eq 'hash' ? {@args} : \@args : @args) }))
  4 100       11  
  9 100       46  
    100          
45             if (scalar @args);
46 27         94 @return;
47 99         359 });
48             }
49              
50 9     5   41 make_keyword($called, 'auto_build', sub { $object{$called}{auto_build} = 1; });
  5         292  
51              
52             make_keyword($called, 'extends', sub {
53 3     3   26 my (@args) = @_;
54 3         8 my $extend = caller();
55 3         7 for my $inherit (@args) {
56 3         10 load($inherit);
57 3         5004 push @{*{\*{"${extend}::ISA"}}{ARRAY}}, $inherit;
  3         7  
  3         5  
  3         41  
58 3 50       14 return unless $object{$inherit};
59 3         22 my $bm = Blessed::Merge->new(blessed => 0, same => 0);
60 3         92 $object{$extend} = $bm->merge($object{$extend}, $object{$inherit});
61 3         2789 for my $name (keys %{$object{$extend}{has}}) {
  3         15  
62             make_keyword($extend, $name, sub {
63 12     12   27 my ($self, $value) = @_;
64 12 50 66     92 if ($value && (
      100        
65             $object{$extend}{has}{$name}->{is} eq 'rw'
66             || [split '::', [caller(1)]->[3]]->[-1] =~ m/^new|build|set_defaults|auto_build$/
67             )) {
68             $value = $object{$extend}{has}{$name}->{coerce}($self, $value, $name)
69 10 50       19 if ($object{$extend}{has}{$name}->{coerce});
70             $object{$extend}{has}{$name}->{required}($self, $value, $name)
71 10 50       25 if ($object{$extend}{$name}->{required});
72 10         18 $value = $object{$extend}{has}{$name}->{isa}($value, $name);
73 10         21 $self->{$name} = $value;
74             $object{$extend}{has}{$name}->{trigger}($self, $value, $name)
75 10 50       18 if ($object{$extend}{has}{$name}->{trigger});
76             }
77 12         41 $self->{$name};
78 15         66 });
79             }
80 3         5 for my $name (keys %{$object{$extend}{method}}) {
  3         25  
81 0         0 make_keyword($extend, $name, $object{$called}{method}{$name});
82             }
83             }
84 9         38 });
85              
86             make_keyword($called, 'require_has', sub {
87 2     2   14 my (@args) = @_;
88 2         5 push @{ $object{$called}{require_has} }, @args;
  2         10  
89 9         80 });
90              
91             make_keyword($called, 'require_sub', sub {
92 1     1   5 my (@args) = @_;
93 1         2 push @{ $object{$called}{require_sub} }, @args;
  1         4  
94 9         53 });
95              
96             make_keyword($called, 'require_method', sub {
97 0     0   0 my (@args) = @_;
98 0         0 push @{ $object{$called}{require_sub} }, @args;
  0         0  
99 9         30 });
100              
101 9         26 $object{$called}{has} = {};
102 9         18 $object{$called}{method} = {};
103              
104             make_keyword($called, "method", sub {
105 1     1   3 my ($name, $sub) = @_;
106              
107 1         3 $object{$called}{method}{$name} = $sub;
108 1         4 make_keyword($called, $name, $sub);
109 9         57 });
110              
111 9     26   66 make_keyword($called, 'has', sub { build_attribute($called, @_) });
  26         54  
112              
113             make_keyword($called, "new", sub {
114 9     9   1767 my ($pkg) = shift;
115 9         21 my $self = bless { }, $pkg;
116 9         32 require_has($called);
117 8         26 require_sub($self, $called);
118 8         19 require_method($called);
119 8 100       29 auto_ld($self, $called, 'lazy') if ($object{$called}{lazy});
120 8         25 set_defaults($self, $called);
121 8 50       37 auto_build($self, $called, @_) if ($object{$called}{auto_build});
122 8 50       38 $self->build(@_) if ($self->can('build'));
123 8 100       22 auto_ld($self, $called, 'delay') if ($object{$called}{delay});
124 8         23 return $self;
125 9         48 });
126             }
127              
128             sub build_attribute {
129 31     31 0 70 my ($called, $name, @attrs) = @_;
130              
131 31   100     89 my $ref = ref $name || 'STRING';
132              
133 31         46 my $attribute_extend;
134 31 50       74 if ($name =~ s/^_([a-zA-Z].*)/$1/) {
135 0         0 $attribute_extend = 1;
136             }
137              
138 31 100       62 if ($ref eq 'ARRAY') {
    100          
139 1         2 build_attribute($called, $_, @attrs) for @{ $name };
  1         4  
140             } elsif ($ref eq 'HASH') {
141 1         1 build_attribute($called, $_, %{ $name->{$_} }) for keys %{ $name };
  1         4  
  2         5  
142             }
143              
144 31 50 33     126 if ( !$attribute_extend && $object{$called}{has}{$name} ) {
145 0         0 croak sprintf "%s attribute already defined for %s object.", $name, $called;
146             }
147              
148 31 50       65 if ( scalar @attrs % 2 ) {
149 0         0 croak sprintf "Invalid attribute definition odd number of key/value pairs (%s) passed with %s in %s object", scalar @attrs, $name, $called;
150             }
151              
152 31         143 $object{$called}{has}{$name} = {@attrs};
153              
154             $object{$called}{has}{$name}{is} = 'rw'
155 31 100       74 if (! $object{$called}{has}{$name}{is});
156              
157             $object{$called}{has}{$name}{isa} = $TYPES{all}
158 31 100       62 if (not defined $object{$called}{has}{$name}{isa});
159              
160 31 100       60 if ($object{$called}{has}{$name}{default}) {
161 13 50       70 if ($object{$called}{has}{$name}{default} =~ m/^1$/) {
    50          
162 0         0 $object{$called}{has}{$name}{value} = $object{$called}{has}{$name}{build_default}();
163             } elsif (ref $object{$called}{has}{$name}{default} eq 'CODE') {
164 13         26 $object{$called}{has}{$name}{value} = $object{$called}{has}{$name}{default}();
165             } else {
166             $object{$called}{has}{$name}{value} = $object{$called}{has}{$name}{type} eq 'ordered_hash'
167             ? deep_clone_ordered_hash($object{$called}{has}{$name}{default})
168 0 0       0 : deep_clone($object{$called}{has}{$name}{default});
169             }
170             }
171              
172 31 50       60 if ($object{$called}{has}{$name}{required}) {
173 0         0 $object{$called}{has}{$name}{required} = \&required;
174             }
175              
176 31 100       60 if ($object{$called}{has}{$name}{lazy}) {
177 4         4 push @{$object{$called}{lazy}}, $name;
  4         7  
178             }
179              
180 31 100       49 if ($object{$called}{has}{$name}{delay}) {
181 1         1 push @{$object{$called}{delay}}, $name;
  1         2  
182             }
183              
184             make_keyword($called, $name, sub {
185 60     60   105 my ($self, $value) = @_;
186 60 50 66     252 if (@_ > 1 && (
      100        
187             $object{$called}{has}{$name}->{is} eq 'rw'
188             || [split '::', [caller(1)]->[3]]->[-1] =~ m/^new|build|set_defaults|auto_build$/
189             )) {
190 33 100       51 if (defined $value) {
191             $value = $object{$called}{has}{$name}->{coerce}($self, $value, $name)
192 32 100       72 if ($object{$called}{has}{$name}->{coerce});
193             $object{$called}{has}{$name}{required}($self, $value, $name)
194 32 50       64 if ($object{$called}{$name}->{required});
195 32         69 $value = $object{$called}{has}{$name}{isa}($value, $name);
196 32         69 $self->{$name} = $value;
197             $object{$called}{has}{$name}{trigger}($self, $value, $name)
198 32 50       85 if ($object{$called}{has}{$name}->{trigger});
199             } else {
200 1         2 $self->{$name} = undef;
201             }
202             }
203 60         204 $self->{$name};
204 31 50       179 }) unless $attribute_extend;
205             }
206              
207             sub require_has {
208 9     9 0 18 my ($called) = shift;
209 9         21 for (@{ $object{$called}{require_has} }) {
  9         31  
210             croak sprintf "The required %s attribute is not defined in the %s object.", $_, $called
211 6 100       208 if (! $object{$called}{has}{$_} );
212             }
213             }
214              
215             sub require_sub {
216 8     8 0 20 my ($self, $called) = @_;
217 8         10 for (@{ $object{$called}{require_sub} }) {
  8         17  
218 1 50       10 croak sprintf "The required %s sub is not defined in the %s object.", $_, $called
219             if (! $self->can($_) );
220             }
221             }
222              
223             sub require_method {
224 8     8 0 16 my ($called) = shift;
225 8         12 for (@{ $object{$called}{require_method} }) {
  8         19  
226             croak sprintf "The required %s method is not defined in the %s object.", $_, $called
227 0 0       0 if (! $object{$called}{method}{$_} );
228             }
229             }
230              
231             sub set_defaults {
232 8     8 0 17 my ($self, $called) = @_;
233             map {
234             defined $object{$called}{has}{$_}{value} && $self->$_($object{$called}{has}{$_}{type} eq 'ordered_hash'
235             ? deep_clone_ordered_hash($object{$called}{has}{$_}{value})
236 46 100       155 : deep_clone($object{$called}{has}{$_}{value}))
    100          
237 73   66     244 } sort { ($object{$called}{has}{$a}{build_order} || $LAST) <=> ($object{$called}{has}{$b}{build_order} || $LAST) }
      66        
238 8         13 keys %{$object{$called}{has}};
  8         36  
239 8         54 return $self;
240             }
241              
242             sub auto_build {
243 8 50   8 0 41 my ($self, $called, %build) = (shift, shift, scalar @_ == 1 ? %{ $_[0] } : @_);
  0         0  
244             map {
245 15 50       67 if ($self->can($_)) {
246 15         38 $self->$_($build{$_});
247             }
248 8   66     39 } sort { ($object{$called}{has}{$a}{build_order} || $LAST) <=> ($object{$called}{has}{$b}{build_order} || $LAST) }
  11   66     63  
249             keys %build;
250             }
251              
252             sub auto_ld {
253 2     2 0 4 my ($self, $called, $type) = @_;
254             map {
255 5 50 33     40 my $cb_value = ref $object{$called}{has}{$_}{$type} || $object{$called}{has}{$_}{$type} !~ m/^1$/ ? $object{$called}{has}{$_}{$type} : $object{$called}{has}{$_}{build_default}->();
256 5         11 $self->$_($cb_value);
257             } sort {
258 4   33     14 ($object{$called}{has}{$a}{build_order} || $LAST) <=> ($object{$called}{has}{$b}{build_order} || $LAST)
      33        
259 2         2 } @{ $object{$called}{$type} };
  2         6  
260             }
261              
262             sub required {
263 0     0 1 0 my ($self, $value, $name) = @_;
264 0 0       0 if ( not defined $value ) {
265 0         0 croak sprintf "No defined value passed to the required %s attribute.",
266             $name;
267             }
268             }
269              
270 0     0 1 0 sub any { $_[0] }
271              
272 1     1 0 1 sub build_string { "" }
273              
274             sub string {
275 6     6 1 11 my ($value, $name) = @_;
276 6 50       12 if (ref $value) {
277 0         0 croak sprintf "The value passed to the %s attribute does not match the string type constraint.",
278             $name;
279             }
280 6         10 return $value;
281             }
282              
283 4     4 0 6 sub build_integer { 0 }
284              
285             sub integer {
286 12     12 1 28 my ($value, $name) = @_;
287 12 50 33     76 if (ref $value || $value !~ m/^\d+$/) {
288 0         0 croak sprintf "The value passed to the %s attribute does not match the type constraint.",
289             $name;
290             }
291 12         33 return $value;
292             }
293              
294 0     0 0 0 sub build_float { 0.00 }
295              
296             sub float {
297 0     0 1 0 my ($value, $name) = @_;
298 0 0 0     0 if (ref $value || $value !~ m/^\d+\.\d+$/) {
299 0         0 croak sprintf "The value passed to the %s attribute does not match the float constraint.",
300             $name;
301             }
302 0         0 return $value;
303             }
304              
305 0     0 0 0 sub build_scalarref { \"" }
306              
307             sub scalarref {
308 0     0 1 0 my ($value, $name) = @_;
309 0 0       0 if (ref $value ne 'SCALAR' ) {
310 0         0 croak sprintf "The value passed to the %s attribute does not match the scalarref constraint.",
311             $name;
312             }
313 0         0 return $value;
314             }
315              
316 0     0 0 0 sub build_boolean { \1 }
317              
318             sub boolean {
319 2     2 1 6 my ($value, $name) = @_;
320 2 50       5 if (! ref $value) {
321 2         19 $value = \!!$value;
322             }
323 2 50       30 if (ref $value ne 'SCALAR' ) {
324 0         0 croak sprintf "The value passed to the %s attribute does not match the scalarref constraint.",
325             $name;
326             }
327 2         5 return $value;
328             }
329              
330 0     0 0 0 sub build_ordered_hash { { } }
331              
332 7     7 1 27 sub ordered_hash { hash(@_); }
333              
334 0     0 0 0 sub build_hash { {} }
335              
336             sub hash {
337 15     15 1 28 my ($value, $name) = @_;
338 15 50       41 if (ref $value ne 'HASH') {
339 0         0 croak sprintf "The value passed to the %s attribute does not match the hash type constraint.",
340             $name;
341             }
342 15         24 return $value;
343             }
344              
345 0     0 0 0 sub build_array { [] }
346              
347             sub array {
348 7     7 1 15 my ($value, $name) = @_;
349 7 50       25 if (ref $value ne 'ARRAY') {
350 0         0 croak sprintf "The value passed to the %s attribute does not match the array type constraint.",
351             $name;
352             }
353 7         13 return $value;
354             }
355              
356             sub fh {
357 0     0 1 0 my ($value, $name) = @_;
358 0 0       0 if (ref $value ne 'GLOB') {
359 0         0 croak sprintf "The value passed to the %s attribute does not match the glob type constraint.",
360             $name;
361             }
362 0         0 return $value;
363             }
364              
365 0     0 0 0 sub build_object { { } }
366              
367             sub object {
368 0     0 1 0 my ($value, $name) = @_;
369 0 0 0     0 if ( ! ref $value || ref $value !~ m/SCALAR|ARRAY|HASH|GLOB/) {
370 0         0 croak sprintf "The value passed to the %s attribute does not match the object type constraint.",
371             $name;
372             }
373 0         0 return $value;
374             }
375              
376             sub deep_clone {
377 211     211 0 280 my ($data) = @_;
378 211         238 my $ref = ref $data;
379 211 100       311 if (!$ref) { return $data; }
  145 50       428  
    100          
    50          
380 0         0 elsif ($ref eq 'SCALAR') { my $r = deep_clone($$data); return \$r; }
  0         0  
381 22         25 elsif ($ref eq 'ARRAY') { return [ map { deep_clone($_) } @{ $data } ]; }
  77         90  
  22         36  
382 44         46 elsif ($ref eq 'HASH') { return { map +( $_ => deep_clone($data->{$_}) ), keys %{ $data } }; }
  44         135  
383 0         0 return $data;
384             }
385              
386             sub deep_clone_ordered_hash {
387 11 100   11 0 38 my (@hash) = scalar @_ == 1 ? %{ $_[0] } : @_;
  7         53  
388 11         160 my %hash = ();
389 11         53 tie(%hash, 'Tie::IxHash');
390 11         177 while (@hash) {
391 33         350 my ($key, $value) = (shift @hash, shift @hash);
392 33         57 $hash{$key} = deep_clone($value)
393             }
394 11         138 return \%hash;
395             }
396              
397             sub load {
398 3     3 0 5 my ($module) = shift;
399 3         8 $module =~ s/\:\:/\//g;
400 3         2299 require $module . '.pm';
401             }
402              
403             1
404              
405             __END__