File Coverage

blib/lib/YAOO.pm
Criterion Covered Total %
statement 241 289 83.3
branch 84 128 65.6
condition 27 49 55.1
subroutine 45 59 76.2
pod 12 34 35.2
total 409 559 73.1


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