File Coverage

blib/lib/YAOO.pm
Criterion Covered Total %
statement 204 251 81.2
branch 71 114 62.2
condition 27 49 55.1
subroutine 40 54 74.0
pod 12 32 37.5
total 354 500 70.8


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