File Coverage

blib/lib/VSO.pm
Criterion Covered Total %
statement 296 354 83.6
branch 100 144 69.4
condition 37 60 61.6
subroutine 55 93 59.1
pod 1 14 7.1
total 489 665 73.5


line stmt bran cond sub pod time code
1              
2             package VSO;
3              
4 9     9   12049 use strict;
  9         18  
  9         374  
5 9     9   50 use warnings 'all';
  9         14  
  9         440  
6 9     9   50 use Carp qw( confess );
  9         20  
  9         765  
7 9     9   70 use Scalar::Util qw( weaken openhandle );
  9         16  
  9         1375  
8 9     9   28801 use Data::Dumper;
  9         217059  
  9         940  
9 9     9   104 use base 'Exporter';
  9         17  
  9         1094  
10 9     9   6151 use VSO::Subtype;
  9         101  
  9         1162  
11              
12             our $VERSION = '0.025';
13              
14             our @EXPORT = qw(
15             has
16             before
17             after
18             extends
19            
20             subtype as where message
21             coerce from via
22             enum
23             );
24              
25             my $_meta = { };
26             my $_coercions = { };
27              
28             sub import
29             {
30             # Turn on strict and warnings in the caller:
31 26     26   25298 import warnings;
32 26         47 $^H |= 1538;
33 26         42 my $class = shift;
34 26         51 my %args = @_;
35 26         57 my $caller = caller;
36 26 100       86 return if $caller eq __PACKAGE__;
37 9     9   56 no strict 'refs';
  9         18  
  9         1967  
38 264         1434 map {
39 22         43 *{"$caller\::$_"} = \&{$_}
  264         259  
  264         473  
40             } @EXPORT;
41 22 100       77 push @{"$caller\::ISA"}, $class if $class eq __PACKAGE__;
  20         6523  
42 22   50     134 $args{extends} ||= [ ];
43 22 50 33     148 $args{extends} = [$args{extends}] if $args{extends} && ! ref($args{extends});
44 22         31 push @{"$caller\::ISA"}, grep { load_class($_); 1 } @{ $args{extends} };
  22         60  
  0         0  
  0         0  
  22         48  
45            
46 22   33     103 $_meta->{ $caller } ||= _new_meta();
47 9     9   49 no warnings 'redefine';
  9         15  
  9         40496  
48 22     82   99 *{"$caller\::meta"} = sub { $_meta->{$caller} };
  22         120  
  82         187  
49            
50 22 50       35 _extend_class( $caller => @{$args{extends}} ) if @{$args{extends}};
  0         0  
  22         5044  
51             }# end import()
52              
53              
54             sub new
55             {
56 33     33 0 13277 my ($class, %args) = @_;
57            
58 33         84 my $s = bless \%args, $class;
59 33         129 $s->_build();
60 27 50       237 $s->BUILD() if $s->can('BUILD');
61            
62 27         154 return $s;
63             }# end new()
64              
65              
66             sub _build
67             {
68 33     33   50 my $s = shift;
69            
70 33         56 my $class = ref($s);
71 33         113 my $meta = $class->meta();
72 33   100     189 $meta->{field_names} ||= [ sort keys %{ $meta->{fields} } ];
  17         130  
73 33         58 my $fields = $meta->{fields};
74            
75 33         40 FIELD: foreach my $name ( @{ $meta->{field_names} } )
  33         80  
76             {
77 57         158 my $props = $fields->{$name};
78 57         215 my $value = _build_arg( $s, $name, $s->{$name}, $props );
79            
80 51 50       118 if( $props->{weak_ref} )
81             {
82 0         0 weaken( $s->{$name} = $value );
83             }
84             else
85             {
86 51         137 $s->{$name} = $value;
87             }# end if()
88             }# end foreach()
89             }# end _build()
90              
91              
92             sub _build_arg
93             {
94 61     61   111 my ($s, $name, $value, $props) = @_;
95            
96             # No value, no default and it's required:
97 61 100 66     569 if( $props->{required} && (! defined($value) ) && (! $props->{default}) )
    100 100        
      100        
98             {
99 4         507 confess "Required param '$name' is required but was not provided.";
100             }
101             # No value, but we have a default:
102             elsif( $props->{default} && (! defined($value) ) )
103             {
104 2 50       11 if( $props->{lazy} )
105             {
106             # Deal with this later.
107 0         0 return;
108             }
109             else
110             {
111 2         9 return $s->{$name} = $value = $props->{default}->( $s );
112             }# end if()
113             }# end if()
114            
115 55         192 $value = _validate_field( $s, $name, $value, $props );
116            
117 52 50 33     161 if( $props->{where} && defined($value) )
118             {
119 0         0 local $_ = $value;
120 0 0       0 confess "Invalid value for property '$name': '$_'"
121             unless $props->{where}->( $s );
122             }# end if()
123            
124 52         106 return $value;
125             }# end _build_arg()
126              
127              
128             sub _validate_field
129             {
130 55     55   85 my ($s, $name, $new_value, $props) = @_;
131            
132 55         130 my $original_type = VSO::Subtype->find(_discover_type( $new_value ));
133 55         82 my $original_value = $new_value;
134            
135 55         66 my $is_ok = 0;
136 55         203 ISA: foreach my $isa ( split /\|/, $props->{isa} )
137             {
138 62 100       173 $isa = "$1\::of::$2" if $isa =~ m{^(.+?)\[(.+?)\]};
139 62         99 TYPE_CHECK: {
140 62         134 my $current_type = VSO::Subtype->find( _discover_type( $new_value ) );
141 62         165 my $wanted_type = VSO::Subtype->find( $isa );
142            
143             # Don't worry about Undef when the field isn't required:
144 62 50       1414 if( $current_type eq 'Undef' )
145             {
146 0 0 0     0 if( (! $props->{required}) || ( ! defined $original_value ) )
147             {
148 0         0 $is_ok = 1;
149 0         0 last ISA;
150             }# end if()
151             }# end if()
152            
153             # Verify that the value matches the entire chain of dependencies:
154 62 100 100     736 if( $wanted_type eq 'Any' || $current_type->isa( $wanted_type ) )
    100 100        
    100          
155             {
156 21 50       517 if( my $ref = $wanted_type->can('where') )
157             {
158 21         41 local $_ = $new_value;
159 21 50       68 if( $wanted_type->where( $s ) )
160             {
161 21         25 $is_ok = 1;
162 21         53 last ISA;
163             }# end if()
164             }
165             else
166             {
167 0         0 $is_ok = 1;
168 0         0 last ISA;
169             }# end if()
170             }
171             elsif( my $can_coerce = $props->{coerce} && exists($_coercions->{ $wanted_type }->{ $current_type }) )
172             {
173             # Can we coerce from this type to the wanted type?:
174 14 50       36 my $coercion = $can_coerce ? $_coercions->{ $wanted_type }->{ "$current_type" } : undef;
175 14         23 local $_ = $new_value;
176 14 50       32 if( $coercion )
177             {
178 14         34 $new_value = $coercion->( $s );
179 14         78 $is_ok = 1;
180             }
181             else
182             {
183 0         0 next TYPE_CHECK;
184             }# end if()
185             }
186 27         67 elsif( eval { $wanted_type->as eq $current_type } )
187             {
188 18         24 local $_ = $new_value;
189 18 100       43 if( $wanted_type->where( $s ) )
190             {
191 17         34 $is_ok = 1;
192 17         36 last ISA;
193             }# end if()
194             }# end if()
195              
196             };
197 24         59 next ISA;
198             }# end foreach()
199            
200 55 100       135 unless( $is_ok )
201             {
202 3         6 local $_ = $original_value;
203 3   50     5 confess "Invalid value for @{[ref($s)]}.$name: isn't a $props->{isa}: [$original_type] '$_'" . (eval{ ': ' . $props->{isa}->message($s) }||'');
  3         24  
204             }# end unless()
205            
206 52         112 return $new_value;
207             }# end _validate_field()
208              
209              
210             sub extends(@)
211             {
212 4     4 0 21 my $class = caller;
213            
214 4         13 _extend_class($class => @_);
215             }# end extends()
216              
217              
218             sub _extend_class
219             {
220 4     4   7 my $class = shift;
221            
222 9     9   84 no strict 'refs';
  9         18  
  9         8778  
223 4         23 my $meta = $class->meta();
224 4         12 map {
225 4         8 load_class( $_ );
226 4         5 push @{"$class\::ISA"}, $_;
  4         68  
227 4 50       18 my $parent_meta = $_->meta or die "Class $_ has no meta!";
228 1         4 map {
229 4         12 $meta->{fields}->{$_} = $parent_meta->{fields}->{$_}
230 4         6 } keys %{ $parent_meta->{fields} };
231 0         0 map {
232 4         18 $meta->{triggers}->{$_} = $parent_meta->{triggers}->{$_}
233 4         6 } keys %{ $parent_meta->{triggers} };
234             } @_;
235             }# end _extend_class()
236              
237              
238             sub before($&)
239             {
240 6     6 0 13 my $class = caller;
241 6         10 my ($name, $sub) = @_;
242 6         13 my $meta = $class->meta;
243            
244             # Sanity:
245 6 50 66     84 confess "You must define property $class.$name before adding triggers to it"
246             unless exists($meta->{fields}->{$name}) || $class->can($name);
247            
248 6 100       18 if( exists($meta->{fields}->{$name}) )
249             {
250 2   50     87 $meta->{triggers}->{"before.$name"} ||= [ ];
251 2         4 push @{ $meta->{triggers}->{"before.$name"} }, $sub;
  2         10  
252             }
253             else
254             {
255 4         15 my $orig = $class->can($name);
256 9     9   58 no strict 'refs';
  9         32  
  9         290  
257 9     9   46 no warnings 'redefine';
  9         15  
  9         10525  
258 4         18 *{"$class\::$name"} = sub {
259 6     6   17 $sub->( @_ );
260 6         353 $orig->( @_ );
261 4         14 };
262             }# end if()
263             }# end before()
264              
265              
266             sub after($&)
267             {
268 6     6 0 12 my $class = caller;
269 6         9 my ($name, $sub) = @_;
270 6         14 my $meta = $class->meta;
271            
272             # Sanity:
273 6 50 66     46 confess "You must define property $class.$name before adding triggers to it"
274             unless exists($meta->{fields}->{$name}) || $class->can($name);
275            
276 6 100       16 if( exists($meta->{fields}->{$name}) )
277             {
278 2   50     21 $meta->{triggers}->{"after.$name"} ||= [ ];
279 2         4 push @{ $meta->{triggers}->{"after.$name"} }, $sub;
  2         9  
280             }
281             else
282             {
283 4         14 my $orig = $class->can($name);
284 9     9   64 no strict 'refs';
  9         17  
  9         2260  
285 9     9   63 no warnings 'redefine';
  9         25  
  9         5322  
286 4         18 *{"$class\::$name"} = sub {
287 6 100   6   522 my $context = defined(wantarray) ? wantarray ? 'list' : 'scalar' : 'void';
    100          
288 6         7 my ($res,@res);
289 6 100       29 $context eq 'list' ? @res = $orig->( @_ ) : $context eq 'scalar' ? $res = $orig->( @_ ) : $orig->( @_ );
    100          
290 6         33 $sub->( @_ );
291 6 100       367 $context eq 'list' ? return @res : $context eq 'scalar' ? return $res : return;
    100          
292 4         30 };
293             }# end if()
294             }# end after()
295              
296              
297             sub has($;@)
298             {
299 29     29 0 526 my $class = caller;
300 29         45 my $name = shift;
301 29         111 my %properties = @_;
302 29         138 my $meta = $class->meta;
303            
304 29   100     97 $properties{isa} ||= 'Any';
305 29         94 $properties{isa} =~ s{^Maybe\[(.*?)\]$}{Undef|$1}s;
306              
307 29         113 foreach my $type ( split /\|/, $properties{isa} )
308             {
309 32 100       135 if( my ($reftype, $valtype) = $type =~ m{^((?:Hash|Array)Ref)\[(.+?)\]$} )
310             {
311 6 50       30 load_class($valtype)
312             unless VSO::Subtype->find($valtype);
313 6         42 (my $classname = $type) =~ s{^(.+?)\[(.+?)\]}{$1 . "::of::$2"}e;
  6         34  
314 6 50       27 unless( VSO::Subtype->subtype_exists($type) )
315             {
316 6         19 _add_collection_subtype( $type, $reftype, $valtype );
317             }# end unless()
318             }
319             else
320             {
321 26 50       116 load_class($type)
322             unless VSO::Subtype->find($type);
323             }# end if()
324             }# end foreach()
325            
326 29         286 my $props = $meta->{fields}->{$name} = {
327             is => 'rw',
328             required => 1,
329             isa => 'Any',
330             lazy => 0,
331             weak_ref => 0,
332             coerce => 0,
333             %properties,
334             };
335            
336 9     9   61 no strict 'refs';
  9         17  
  9         21320  
337 29         266 *{"$class\::$name"} = sub {
338 23     23   808 my $s = shift;
339            
340             # Getter:
341 23 100       77 unless( @_ )
342             {
343             # Support laziness:
344 18 50 33     70 if( ( ! defined($s->{$name}) ) && $props->{default} )
345             {
346 0 0       0 if( $props->{weak_ref} )
347             {
348 0         0 weaken($s->{$name} = $props->{default}->( $s ));
349             }
350             else
351             {
352 0         0 $s->{$name} = $props->{default}->( $s );
353             }# end if()
354             }# end if()
355            
356 18         254 return $s->{$name};
357             }# end unless()
358            
359 5 100       34 if( $props->{is} eq 'ro' )
    50          
360             {
361 1         118 confess "Cannot change readonly property '$name'";
362             }
363             elsif( $props->{is} eq 'rw' )
364             {
365 4         11 my $new_value = shift;
366 4         9 my $old_value = $s->{$name};
367            
368 4         15 $new_value = _build_arg( $s, $name, $new_value, $props );
369            
370 3 100       19 if( my $triggers = $meta->{triggers}->{"before.$name"} )
371             {
372 1         3 map {
373 1         3 $_->( $s, $new_value, $old_value );
374             } @$triggers;
375             }# end if()
376            
377             # Now change the value:
378 3 50       91 if( $props->{weak_ref} )
379             {
380 0         0 weaken($s->{$name} = $new_value);
381             }
382             else
383             {
384 3         9 $s->{$name} = $new_value;
385             }# end if()
386            
387 3 100       16 if( my $triggers = $meta->{triggers}->{"after.$name"} )
388             {
389 1         6 map {
390 1         2 $_->( $s, $s->{$name}, $old_value);
391             } @$triggers;
392             }# end if()
393            
394             # Default to returning the new value:
395 3 100       84 $new_value if defined wantarray();
396             }# end if()
397 29         863 };
398             }# end has()
399              
400              
401             sub _add_collection_subtype
402             {
403 6     6   13 my ($type, $reftype, $valtype) = @_;
404            
405             _add_subtype(
406             'name' => $type,
407             'as' => $reftype,
408             'where' =>
409             $reftype eq 'ArrayRef' ?
410             sub {
411 2     2   4 my $vals = $_;
412             # Handle an empty value:
413 2 50       5 return 1 unless @$vals;
414 2         4 ! grep {! _discover_type($_)->isa($valtype) } @$vals
  4         7  
415             }
416             :
417             sub {
418 7     7   19 my $vals = [ values %$_ ];
419             # Handle an empty value:
420 7 50       22 return 1 unless @$vals;
421 7         9 ! grep {! _discover_type($_)->isa($valtype) } @$vals
  7         11  
422             },
423 0     0   0 'message' => sub { "Must be a valid '$type'" },
424 6 100       75 );
425             }# end _add_collection_subtype()
426              
427              
428             sub _discover_type
429             {
430 128     128   162 my ($val) = @_;
431            
432 128 100       331 if( my $ref = ref($val) )
433             {
434 39 50       70 return 'ScalarRef' if $ref eq 'SCALAR';
435 39 100       114 return 'ArrayRef' if $ref eq 'ARRAY';
436 35 100       99 return 'HashRef' if $ref eq 'HASH';
437 21 100       58 return 'CodeRef' if $ref eq 'CODE';
438 13 50       24 return 'GlobRef' if $ref eq 'GLOB';
439 13 50       32 return 'RegexpRef' if $ref eq 'Regexp';
440 13 50       38 return 'FileHandle' if openhandle($val);
441             # Otherwise, it's a reference to some kind of object:
442 13         84 return $ref;
443             }
444             else
445             {
446 89 50       165 return 'Undef' unless defined($val);
447 89 50       275 return 'Bool' if $val =~ m{^(?:0|1)$};
448 89 100       279 return 'Int' if $val =~ m{^\d+$};
449 75 50       207 return 'Num' if $val =~ m{^\d+\.?\d*?$};
450             # ClassName?:
451 75         151 (my $fn = "$val.pm") =~ s{::}{/}g;
452 75 50       170 return 'ClassName' if exists($INC{$fn});
453 75         302 return 'Str';
454             }# end if()
455             }# end _discover_type()
456              
457              
458             sub _new_meta
459             {
460             return {
461 22     22   127 fields => { },
462             triggers => { }
463             };
464             }# end _new_meta()
465              
466              
467             sub load_class
468             {
469 4     4 0 7 my $class = shift;
470            
471 4         12 (my $file = "$class.pm") =~ s|::|/|g;
472 9     9   67 no strict 'refs';
  9         29  
  9         6725  
473 4 50 33     7 eval { require $file unless defined(@{"$class\::ISA"}) || $INC{$file}; 1 }
  4 50       5  
  4         22  
  4         13  
474             or die "Can't require $file: $@";
475 4   33     27 $INC{$file} ||= $file;
476 4         41 $class->import(@_);
477             }# end load_class()
478              
479              
480             sub _add_subtype
481             {
482 189     189   1009 my %args = @_;
483              
484 189 100       516 $args{name} =~ s{^(.+?)\[(.+?)\]}{"$1" . "::of::$2"}e
  6         25  
485             if $args{name} =~ m{^.+?\[.+?\]$};
486              
487 189 50       426 return if $VSO::Subtype::types{$args{name}};
488            
489 189   100     672 $args{as} ||= '';
490 189 50       390 $args{as} =~ s{^(.+?)\[(.+?)\]}{"$1" . "::of::$2"}e
  0         0  
491             if $args{as} =~ m{^.+?\[.+?\]$};
492            
493 189         280 my $name = $args{name};
494 9     9   63 no strict 'refs';
  9         25  
  9         10488  
495            
496 189         307 @{"$name\::ISA"} = (grep { $_ } 'VSO::Subtype', $args{as});
  189         4739  
  378         592  
497 189     189   803 *{"$name\::name"} = sub{$name};
  189         781  
  189         767  
498 189     25   684 *{"$name\::as"} = sub{$args{as}};
  189         631  
  25         87  
499 189         272 *{"$name\::where"} = $args{where};
  189         595  
500 189         368 *{"$name\::message"} = $args{message};
  189         601  
501 189         412 (my $file = "$name.pm") =~ s|::|/|g;
502 189         513 $INC{$file} = $file;
503 189         1292 $name->init();
504             }# end _add_subtype()
505              
506              
507             sub subtype($;@)
508             {
509 182     182 0 548 my ($name, %args) = @_;
510            
511 182 50       523 confess "Subtype '$name' already exists"
512             if $VSO::Subtype::types{$name};
513             _add_subtype(
514             name => $name,
515             as => $args{as},
516 0     0   0 where => $args{where} || sub { 1 },
517 0     0   0 message => $args{message} || sub { "Must be a valid '$name'" },
518 182   50     815 );
      100        
519             }# end subtype()
520 184     184 0 732 sub as { as => shift, @_ }
521 184     184 0 926 sub where(&) { where => $_[0] }
522 179     179 0 552 sub message(&) { message => $_[0] }
523              
524              
525             sub coerce($;@)
526             {
527 10     10 0 30 my ($to, %args) = @_;
528            
529 10         36 my ($pkg,$filename,$line) = caller;
530 10 50       71 confess "Coercion from '$args{from}' to '$to' is already defined in $filename line $line"
531             if defined($_coercions->{$to}->{$args{from}});
532 10         47 $_coercions->{$to}->{$args{from}} = $args{via};
533             }# end coerce()
534 10     10 1 55 sub from { from => shift, @_ }
535 10     10 0 151 sub via(&) { via => $_[0] }
536              
537              
538             sub enum($$)
539             {
540 1     1 0 13 my ($name, $vals) = @_;
541             _add_subtype(
542             name => $name,
543             as => 'Str',
544             where => sub {
545 2     2   4 my $val = $_;
546 9     9   62 no warnings 'uninitialized';
  9         16  
  9         38207  
547 2         5 for( @$vals ) {
548 9 100       27 return 1 if $_ eq $val;
549             }
550 1         4 return 0;
551             },
552             message => sub {
553 1     1   246 "Must be a valid '$name'"
554             }
555 1         21 );
556             }# end enum($$)
557              
558              
559             # All things spring forth from the formless void:
560              
561             subtype 'Any' =>
562             as '',
563 4     4   10 where { 1 },
564 0     0   0 message { '' };
565              
566             subtype 'Item' =>
567             as 'Any',
568 0     0   0 where { 1 },
569 0     0   0 message { '' };
570              
571             subtype 'Undef' =>
572             as 'Item',
573 0     0   0 where { ! defined },
574 0     0   0 message { "Must not be defined" };
575              
576             subtype 'Defined' =>
577             as 'Item',
578 0     0   0 where { defined },
579 0     0   0 message { "Must be defined" };
580              
581             subtype 'Value' =>
582             as 'Defined',
583 0     0   0 where { ! ref },
584 0     0   0 message { "Cannot be a reference" };
585              
586             subtype 'Str' =>
587             as 'Value',
588 13     13   35 where { 1 },
589 0     0   0 message { '' };
590              
591             subtype 'Num' =>
592             as 'Str',
593 0     0   0 where { m{^[\+\-]?\d+\.?\d*?$} },
594 0     0   0 message { 'Must contain only numbers and decimals' };
595              
596             subtype 'Int' =>
597             as 'Num',
598 2     2   12 where { m{^[\+\-]?\d+$} },
599 0     0     message { 'Must contain only numbers 0-9' };
600              
601             subtype 'Bool' =>
602             as 'Int',
603 0 0   0     where { ( ! defined($_) ) || m{^(?:1|0)$} },
604 0     0     message { "Must be a 1 or a 0" };
605              
606             subtype 'ClassName' =>
607             as 'Str',
608 0     0     where { m{^[a-z\:0-9_]+$}i },
609 0     0     message { 'Must match m{^[a-z\:0-9_]+$}i' };
610              
611             subtype 'Ref' =>
612             as 'Defined',
613 0     0     where { ref },
614 0     0     message { 'Must be a reference' };
615              
616             subtype 'ScalarRef' =>
617             as 'Ref',
618 0     0     where { ref($_) eq 'SCALAR' },
619 0     0     message { 'Must be a scalar reference (ScalarRef)' };
620              
621             subtype 'ArrayRef' =>
622             as 'Ref',
623 0     0     where { ref($_) eq 'ARRAY' },
624 0     0     message { 'Must be an array reference (ArrayRef)' };
625              
626 0     0     subtype 'HashRef' =>
627             as 'Ref',
628             where {ref($_) eq 'HASH' },
629 0     0     message { 'Must be a hash reference (HashRef)' };
630              
631             subtype 'CodeRef' =>
632             as 'Ref',
633 0     0     where { ref($_) eq 'CODE' },
634 0     0     message { 'Must be a code reference (CodeRef)' };
635              
636             subtype 'RegexpRef' =>
637             as 'Ref',
638 0     0     where { ref($_) eq 'Regexp' },
639 0     0     message { 'Must be a Regexp' };
640              
641             subtype 'GlobRef' =>
642             as 'Ref',
643 0     0     where { ref($_) eq 'GLOB' },
644 0     0     message { 'Must be a GlobRef (GLOB)' };
645            
646             subtype 'FileHandle' =>
647             as 'GlobRef',
648 0     0     where { openhandle($_) },
649 0     0     message { 'Must be a FileHandle' };
650            
651             subtype 'Object' =>
652             as 'Ref',
653 9     9   81 where { no strict 'refs'; scalar(@{ref($_) . "::ISA"}) },
  9     0   24  
  9         1708  
  0            
  0            
654 0     0     message { 'Must be an object' };
655              
656             1;# return true:
657              
658              
659              
660             =pod
661              
662             =head1 NAME
663              
664             VSO - Very Simple Objects
665              
666             =head1 DEPRECATED
667              
668             Do not use. Look at L, L, L or L instead.
669              
670             =head1 SYNOPSIS
671              
672             Basic point example:
673              
674             package Plane;
675             use VSO;
676            
677             has 'width' => (
678             is => 'ro',
679             isa => 'Int',
680             );
681            
682             has 'height' => (
683             is => 'ro',
684             isa => 'Int',
685             );
686            
687             has 'points' => (
688             is => 'rw',
689             isa => 'ArrayRef[Point2d]',
690             required => 0,
691             );
692              
693              
694             package Point2d;
695             use VSO;
696            
697             subtype 'ValidValue'
698             => as 'Int'
699             => where { $_ >= 0 && $_ <= shift->plane->width }
700             => message { 'Value must be between zero and ' . shift->plane->width };
701            
702             has 'plane' => (
703             is => 'ro',
704             isa => 'Plane',
705             weak_ref => 1,
706             );
707            
708             has 'x' => (
709             is => 'rw',
710             isa => 'ValidValue'
711             );
712            
713             has 'y' => (
714             is => 'rw',
715             isa => 'ValidValue'
716             );
717            
718             after 'x' => sub {
719             my ($s, $new_value, $old_value) = @_;
720             warn "Moving $s from x$old_value to x$new_value";
721             };
722            
723             after 'y' => sub {
724             my ($s, $new_value, $old_value) = @_;
725             warn "Moving $s from y$old_value to y$new_value";
726             };
727              
728             Fancy 3D Point:
729              
730             package Point3d;
731             use VSO;
732            
733             extends 'Point2d';
734            
735             has 'z' => (
736             is => 'rw',
737             isa => 'Int',
738             );
739              
740             sub greet { warn "Hello, World!" }
741            
742             before 'greet' => sub {
743             warn "About to greet you";
744             };
745            
746             after 'greet' => sub {
747             warn "I have greeted you";
748             };
749              
750              
751             Enums:
752              
753             package Foo;
754             use VSO;
755              
756             enum 'DayOfWeek' => [qw( Sun Mon Tue Wed Thu Fri Sat )];
757              
758             has 'day' => (
759             is => 'ro',
760             isa => 'DayOfWeek',
761             required => 1,
762             );
763              
764             Coercions and Subtypes:
765              
766             package Ken;
767             use VSO;
768              
769             subtype 'Number::Odd'
770             => as 'Int'
771             => where { $_ % 2 }
772             => message { "$_ is not an odd number: %=:" . ($_ % 2) };
773              
774             subtype 'Number::Even'
775             => as 'Int'
776             => where { (! $_) || ( $_ % 2 == 0 ) }
777             => message { "$_ is not an even number" };
778              
779             coerce 'Number::Odd'
780             => from 'Int'
781             => via { $_ % 2 ? $_ : $_ + 1 };
782              
783             coerce 'Number::Even'
784             => from 'Int'
785             => via { $_ % 2 ? $_ + 1 : $_ };
786              
787             has 'favorite_number' => (
788             is => 'ro',
789             isa => 'Number::Odd',
790             required => 1,
791             coerce => 1, # Otherwise no coercion is performed.
792             );
793              
794             ...
795              
796             my $ken = Ken->new( favorite_number => 3 ); # Works
797             my $ken = Ken->new( favorite_number => 6 ); # Works, because of coercion.
798              
799             Compile-time Extension Syntax new in v0.024:
800              
801             package Root::Foo;
802             use VSO;
803             has ...;
804            
805             package Subclass::Foo;
806             use VSO extends => 'Foo::Class'; # inheritance during compile-time, not runtime.
807            
808             package Subclass::Bar;
809             use VSO extends => [qw( Foo::Class Bar::Class )]; # extend many at once.
810              
811              
812             =head1 DESCRIPTION
813              
814             VSO aims to offer a declarative OO style for Perl with very little overhead, without
815             being overly-minimalist.
816              
817             VSO is a simplified Perl5 object type system I to L, but simpler.
818              
819             =head2 TYPES
820              
821             VSO offers the following type system:
822              
823             Any
824             Item
825             Bool
826             Undef
827             Maybe[`a]
828             Defined
829             Value
830             Str
831             Num
832             Int
833             ClassName
834             Ref
835             ScalarRef
836             ArrayRef
837             HashRef
838             CodeRef
839             RegexpRef
840             GlobRef
841             FileHandle
842             Object
843              
844             The key differences are that everything is derived from C and there are no roles.
845              
846             VSO does not currently support roles. I<(This may change soon.)>
847              
848             =head2 "Another" Moose?
849              
850             Yes, but not exactly. VSO is B intended as a drop-in replacement
851             for Moose, Mouse, Moo or Mo. They are all doing a fantastic job and you should use them.
852              
853             We've got a ways to go before version 1.000 is released, so don't get too excited
854             if the documentation isn't quite finished or it's not clear why VSO was made.
855              
856             =head1 AUTHOR
857              
858             John Drago
859              
860             =head1 LICENSE
861              
862             This software is Free software and may be used and redistributed under the same
863             terms as perl itself.
864              
865             =cut
866