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   768290 use strict; no strict 'refs';
  7     7   16  
  7         243  
  7         27  
  7         21  
  7         245  
3 7     7   31 use warnings;
  7         13  
  7         326  
4 7     7   42 use Carp qw/croak/; use Tie::IxHash;
  7     7   18  
  7         422  
  7         3603  
  7         31881  
  7         307  
5 7     7   47 use feature qw/state/;
  7         13  
  7         1144  
6 7     7   3213 use Blessed::Merge;
  7         164764  
  7         348  
7 7     7   3371 use Hash::Typed;
  7         10553  
  7         41657  
8             our $VERSION = '0.10';
9              
10             our (%TYPES, %object, $LAST);
11              
12             sub make_keyword {
13 379     379 0 650 my ($called, $key, $cb) = @_;
14 379         438 *{"${called}::$key"} = $cb;
  379         1337  
15 379         11927 $LAST = 10000000000000000000;
16             }
17              
18             sub import {
19 11     11   133114 my ($package, @attributes) = @_;
20              
21 11         36 my $called = caller();
22              
23 11         80 strict->import();
24 11         338 warnings->import();
25              
26 11         28 for my $is (qw/ro rw/) {
27 22     23   93 make_keyword($called, $is, sub { is => $is });
  23         155  
28             }
29              
30 11         21 for my $key (qw/isa default coerce required trigger lazy delay build_order/) {
31             make_keyword($called, $key, sub {
32 32     32   67 my (@value) = @_;
33 32 100 100     196 return $key => scalar @value > 1 ? @value : ($value[0] || 1);
34 88         251 });
35             }
36              
37 11         23 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   97 my (@args) = @_;
40             my @return = (
41 27         93 \&{"${package}::${isa}"},
42             type => $isa,
43 27         31 build_default => \&{"${package}::build_${isa}"}
  27         98  
44             );
45 27 100   7   147 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       15  
  9 100       60  
    100          
46             if (scalar @args);
47 27         151 @return;
48 121         454 });
49             }
50              
51             make_keyword($called, 'typed_hash', sub {
52 3     3   555 my (@args) = @_;
53 3         15 my $spec = shift @args;
54 3 50       11 if (! scalar $spec) {
55 0         0 die 'Invalid declaration of a typed_hash no Hash::Typed spec passed'
56             }
57 3 100       13 if (caller(1)) {
58             return Hash::Typed->new(
59             deep_clone($spec),
60 2         16 %{ deep_clone_ordered_hash(@args) }
  2         6  
61             );
62             }
63             my @return = (
64 1         2 \&{"${package}::typed_hash"},
  1         9  
65             type => 'typed_hash',
66             );
67             push @return, default => sub {
68             Hash::Typed->new(
69             deep_clone($spec),
70 1     1   5 %{ deep_clone_ordered_hash(@args) }
  1         5  
71             );
72 1         8 };
73             @return
74 11         88 });
  1         7  
75              
76              
77 11     6   77 make_keyword($called, 'auto_build', sub { $object{$called}{auto_build} = 1; });
  6         1029164  
78              
79             make_keyword($called, 'extends', sub {
80 4     4   28 my (@args) = @_;
81 4         12 my $extend = caller();
82 4         11 for my $inherit (@args) {
83 4         15 load($inherit);
84 4         11235 push @{*{\*{"${extend}::ISA"}}{ARRAY}}, $inherit;
  4         8  
  4         7  
  4         70  
85 4 50       20 return unless $object{$inherit};
86 4         35 my $bm = Blessed::Merge->new(blessed => 0, same => 0);
87 4         145 $object{$extend} = $bm->merge($object{$extend}, $object{$inherit});
88 4         3196 for my $name (keys %{$object{$extend}{has}}) {
  4         47  
89             make_keyword($extend, $name, sub {
90 17     17   42 my ($self, $value) = @_;
91 17 50 66     154 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       35 if ($object{$extend}{has}{$name}->{coerce});
97             $object{$extend}{has}{$name}->{required}($self, $value, $name)
98 13 50       62 if ($object{$extend}{$name}->{required});
99 13         42 $value = $object{$extend}{has}{$name}->{isa}($value, $name, $called);
100 13         49 $self->{$name} = $value;
101             $object{$extend}{has}{$name}->{trigger}($self, $value, $name)
102 13 50       51 if ($object{$extend}{has}{$name}->{trigger});
103             }
104 17         89 $self->{$name};
105 16         121 });
106             }
107 4         8 for my $name (keys %{$object{$extend}{method}}) {
  4         40  
108 0         0 make_keyword($extend, $name, $object{$called}{method}{$name});
109             }
110             }
111 11         61 });
112              
113             make_keyword($called, 'require_has', sub {
114 2     2   8 my (@args) = @_;
115 2         2 push @{ $object{$called}{require_has} }, @args;
  2         9  
116 11         49 });
117              
118             make_keyword($called, 'require_sub', sub {
119 1     1   4 my (@args) = @_;
120 1         1 push @{ $object{$called}{require_sub} }, @args;
  1         2  
121 11         41 });
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         43 });
127              
128 11         34 $object{$called}{has} = {};
129 11         31 $object{$called}{method} = {};
130              
131             make_keyword($called, "method", sub {
132 1     1   2 my ($name, $sub) = @_;
133              
134 1         2 $object{$called}{method}{$name} = $sub;
135 1         2 make_keyword($called, $name, $sub);
136 11         67 });
137              
138 11     27   75 make_keyword($called, 'has', sub { build_attribute($called, @_) });
  27         75  
139              
140             make_keyword($called, "new", sub {
141 13     13   183472 my ($pkg) = shift;
142 13         34 my $self = bless { }, $pkg;
143 13         47 require_has($called);
144 12         40 require_sub($self, $called);
145 12         56 require_method($called);
146 12 100       43 auto_ld($self, $called, 'lazy') if ($object{$called}{lazy});
147 12         61 set_defaults($self, $called);
148 12 50       73 auto_build($self, $called, @_) if ($object{$called}{auto_build});
149 12 50       89 $self->build(@_) if ($self->can('build'));
150 12 100       41 auto_ld($self, $called, 'delay') if ($object{$called}{delay});
151 12         48 return $self;
152 11         85 });
153             }
154              
155             sub build_attribute {
156 32     32 0 100 my ($called, $name, @attrs) = @_;
157              
158 32   100     130 my $ref = ref $name || 'STRING';
159              
160 32         1894 my $attribute_extend;
161 32 50       98 if ($name =~ s/^_([a-zA-Z].*)/$1/) {
162 0         0 $attribute_extend = 1;
163             }
164              
165 32 100       84 if ($ref eq 'ARRAY') {
    100          
166 1         2 build_attribute($called, $_, @attrs) for @{ $name };
  1         21  
167             } elsif ($ref eq 'HASH') {
168 1         2 build_attribute($called, $_, %{ $name->{$_} }) for keys %{ $name };
  1         3  
  2         4  
169             }
170              
171 32 50 33     232 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       68 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         140 $object{$called}{has}{$name} = {@attrs};
180              
181             $object{$called}{has}{$name}{is} = 'rw'
182 32 100       121 if (! $object{$called}{has}{$name}{is});
183              
184             $object{$called}{has}{$name}{isa} = $TYPES{all}
185 32 100       76 if (not defined $object{$called}{has}{$name}{isa});
186              
187 32 100       67 if ($object{$called}{has}{$name}{default}) {
188 14 50       80 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         35 $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       920 if ($object{$called}{has}{$name}{required}) {
200 0         0 $object{$called}{has}{$name}{required} = \&required;
201             }
202              
203 32 100       64 if ($object{$called}{has}{$name}{lazy}) {
204 4         3 push @{$object{$called}{lazy}}, $name;
  4         8  
205             }
206              
207 32 100       87 if ($object{$called}{has}{$name}{delay}) {
208 1         2 push @{$object{$called}{delay}}, $name;
  1         3  
209             }
210              
211             make_keyword($called, $name, sub {
212 65     65   129 my ($self, $value) = @_;
213 65 50 66     338 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       66 if (defined $value) {
218             $value = $object{$called}{has}{$name}->{coerce}($self, $value, $name)
219 35 100       103 if ($object{$called}{has}{$name}->{coerce});
220             $object{$called}{has}{$name}{required}($self, $value, $name)
221 35 50       87 if ($object{$called}{$name}->{required});
222 35         105 $value = $object{$called}{has}{$name}{isa}($value, $name, $called);
223 35         95 $self->{$name} = $value;
224             $object{$called}{has}{$name}{trigger}($self, $value, $name)
225 35 50       88 if ($object{$called}{has}{$name}->{trigger});
226             } else {
227 1         2 $self->{$name} = undef;
228             }
229             }
230 65         331 $self->{$name};
231 32 50       262 }) unless $attribute_extend;
232             }
233              
234             sub require_has {
235 13     13 0 30 my ($called) = shift;
236 13         21 for (@{ $object{$called}{require_has} }) {
  13         54  
237             croak sprintf "The required %s attribute is not defined in the %s object.", $_, $called
238 6 100       248 if (! $object{$called}{has}{$_} );
239             }
240             }
241              
242             sub require_sub {
243 12     12 0 29 my ($self, $called) = @_;
244 12         32 for (@{ $object{$called}{require_sub} }) {
  12         35  
245 1 50       18 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 24 my ($called) = shift;
252 12         41 for (@{ $object{$called}{require_method} }) {
  12         37  
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 42 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       226 : deep_clone($object{$called}{has}{$_}{value}))
    100          
264 73   66     351 } sort { ($object{$called}{has}{$a}{build_order} || $LAST) <=> ($object{$called}{has}{$b}{build_order} || $LAST) }
      66        
265 12         15 keys %{$object{$called}{has}};
  12         86  
266 12         25 return $self;
267             }
268              
269             sub auto_build {
270 12 50   12 0 80 my ($self, $called, %build) = (shift, shift, scalar @_ == 1 ? %{ $_[0] } : @_);
  0         0  
271             map {
272 17 50       83 if ($self->can($_)) {
273 17         40 $self->$_($build{$_});
274             }
275 12   66     42 } sort { ($object{$called}{has}{$a}{build_order} || $LAST) <=> ($object{$called}{has}{$b}{build_order} || $LAST) }
  12   66     75  
276             keys %build;
277             }
278              
279             sub auto_ld {
280 2     2 0 4 my ($self, $called, $type) = @_;
281             map {
282 5 50 33     25 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         10 $self->$_($cb_value);
284             } sort {
285 4   33     19 ($object{$called}{has}{$a}{build_order} || $LAST) <=> ($object{$called}{has}{$b}{build_order} || $LAST)
      33        
286 2         7 } @{ $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 25 my ($value, $name) = @_;
303 6 50       14 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         10 return $value;
308             }
309              
310 4     4 0 5 sub build_integer { 0 }
311              
312             sub integer {
313 12     12 1 22 my ($value, $name) = @_;
314 12 50 33     106 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         32 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 6 my ($value, $name) = @_;
347 2 50       6 if (! ref $value) {
348 2         5 $value = \!!$value;
349             }
350 2 50       6 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         5 return $value;
355             }
356              
357 0     0 0 0 sub build_ordered_hash { { } }
358              
359 7     7 1 21 sub ordered_hash { hash(@_); }
360              
361             sub typed_hash {
362 6     6 0 15 my ($value, $name, $called) = @_;
363              
364 6 100       18 if (ref $value ne 'Hash::Typed') {
365 4         9 my $hash = $object{$called}{has}{$name}{value};
366              
367 4 50       24 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         13 set_typed_hash($hash, $value);
373              
374 4         6 $value = $hash;
375             }
376              
377 6         15 return $value;
378             }
379              
380             sub set_typed_hash {
381 5     5 0 12 my ($hash, $value) = @_;
382              
383 5         31 for my $k (keys %{$value}) {
  5         43  
384 13 100       475 if (ref $hash->{$k} eq 'Hash::Typed') {
385 1         16 $hash->{$k} = set_typed_hash($hash->{$k}, $value->{$k});
386             } else {
387 12         51 $hash->{$k} = $value->{$k};
388             }
389             }
390            
391 5         147 for my $k (keys %{ $hash }) {
  5         16  
392 14 100       79 if (! exists $value->{$k}) {
393 1         7 delete $hash->{$k};
394             }
395             }
396            
397 5         43 return $hash;
398             }
399              
400              
401 0     0 0 0 sub build_hash { {} }
402              
403             sub hash {
404 15     15 1 36 my ($value, $name) = @_;
405 15 50       50 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         27 return $value;
410             }
411              
412 0     0 0 0 sub build_array { [] }
413              
414             sub array {
415 7     7 1 16 my ($value, $name) = @_;
416 7 50       42 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         40 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 440 my ($data) = @_;
445 259         395 my $ref = ref $data;
446 259 100       532 if (!$ref) { return $data; }
  174 50       699  
    100          
    100          
447 0         0 elsif ($ref eq 'SCALAR') { my $r = deep_clone($$data); return \$r; }
  0         0  
448 29         39 elsif ($ref eq 'ARRAY') { return [ map { deep_clone($_) } @{ $data } ]; }
  100         185  
  29         62  
449 49         63 elsif ($ref eq 'HASH') { return { map +( $_ => deep_clone($data->{$_}) ), keys %{ $data } }; }
  49         173  
450 7         54 return $data;
451             }
452              
453             sub deep_clone_ordered_hash {
454 14 100   14 0 80 my (@hash) = scalar @_ == 1 ? %{ $_[0] } : @_;
  7         38  
455 14         212 my %hash = ();
456 14         99 tie(%hash, 'Tie::IxHash');
457 14         296 while (@hash) {
458 39         492 my ($key, $value) = (shift @hash, shift @hash);
459 39         95 $hash{$key} = deep_clone($value)
460             }
461 14         224 return \%hash;
462             }
463              
464             sub load {
465 4     4 0 8 my ($module) = shift;
466 4         11 $module =~ s/\:\:/\//g;
467 4         1561 require $module . '.pm';
468             }
469              
470             1
471              
472             __END__