File Coverage

blib/lib/Class/Monadic.pm
Criterion Covered Total %
statement 143 147 97.2
branch 30 34 88.2
condition 10 14 71.4
subroutine 27 28 96.4
pod 9 9 100.0
total 219 232 94.4


line stmt bran cond sub pod time code
1             package Class::Monadic;
2              
3 10     10   584387 use 5.008_001;
  10         39  
  10         436  
4 10     10   58 use strict;
  10         20  
  10         370  
5 10     10   59 use warnings;
  10         24  
  10         563  
6              
7             our $VERSION = '0.04';
8              
9 10     10   53 use Exporter qw(import);
  10         20  
  10         782  
10             our @EXPORT_OK = qw(monadic);
11             our %EXPORT_TAGS = (all => \@EXPORT_OK);
12              
13 10     10   59 use Carp ();
  10         18  
  10         171  
14 10     10   11422 use Data::Util ();
  10         12154  
  10         246  
15 10     10   104 use Scalar::Util ();
  10         28  
  10         297  
16 10     10   11224 use Hash::FieldHash ();
  10         30041  
  10         3807  
17              
18             #use Class::Method::Modifiers::Fast ();
19              
20             Hash::FieldHash::fieldhash my %Meta;
21              
22             sub _cannot_initialize{
23 3     3   47 Carp::croak 'Cannot initialize a monadic object without object references';
24             }
25              
26             sub monadic{
27 27     27 1 7005 my($object) = @_;
28 27 100       99 ref($object) or _cannot_initialize();
29              
30 26   66     355 return $Meta{$object} ||= __PACKAGE__->_new($object);
31             }
32              
33             sub initialize{
34 22     22 1 83969 my($class, $object) = @_;
35 22 100       4011 ref($object) or _cannot_initialize();
36              
37 21   66     4050 return $Meta{$object} ||= $class->_new($object);
38             }
39              
40              
41             sub _new{
42 32     32   3592 my($metaclass, $object) = @_;
43              
44 32 50       3753 if(Data::Util::is_glob_ref($object)){
45 0         0 $object = *{$object}{IO};
  0         0  
46             }
47              
48 32 100       3900 my $class = Scalar::Util::blessed($object) or _cannot_initialize();
49              
50 31         3982 $class =~ s/ ::0x[a-f0-9]+ \z//xms; # remove its monadic identity (in cloning)
51              
52 31         4115 my $meta = bless {
53             class => $class,
54             id => sprintf('0x%x', Scalar::Util::refaddr($object)),
55              
56             object => $object,
57             isa => undef,
58             sclass => undef,
59             methods => undef,
60             modifiers => undef,
61             fields => undef,
62             field_map => undef,
63             }, $metaclass;
64 31         3511 Scalar::Util::weaken( $meta->{object} );
65              
66 31         8268 &Internals::SvREADONLY($meta, 1); # lock_keys(%{$meta})
67              
68 31         3520 my $sclass = $class . '::' . $meta->{id};
69 10     10   72 my $sclass_isa = do{ no strict 'refs'; \@{$sclass . '::ISA'} };
  10         19  
  10         15000  
  31         9320  
  31         3744  
  31         11441  
70              
71 31         3473 $meta->{sclass} = $sclass;
72 31         3384 $meta->{isa} = $sclass_isa;
73              
74 31         3613 my $base = $metaclass . '::Object';
75 31 100       3852 if($class->can('clone')){
76 4         10 $base .= '::Clonable';
77             }
78 31         3319 @{$sclass_isa} = ($base, $class);
  31         7684  
79              
80 31         3859 bless $object, $sclass; # re-bless
81 31         11320 return $meta;
82             }
83              
84              
85             sub name{
86 4     4 1 7 my($meta) = @_;
87              
88 4         23 return $meta->{class};
89             }
90              
91             sub id{
92 0     0 1 0 my($meta) = @_;
93              
94 0         0 return $meta->{id};
95             }
96              
97             *add_methods = \&add_method; # alias
98             sub add_method{
99 21     21 1 1180 my $meta = shift;
100              
101 21     3   1556 Data::Util::install_subroutine($meta->{sclass}, @_); # dies on fail
  3         3112  
  3         1295  
  3         32  
102              
103 19   100     1359 push @{$meta->{methods} ||= []}, @_;
  19         2509  
104 19         2528 return;
105             }
106              
107             *add_fields = \&add_field; # alias
108             sub add_field{
109 13     13 1 958 my $meta = shift;
110              
111 13         1212 my $fields_ref = Data::Util::mkopt_hash(\@_, 'add_field', [qw(Regexp ARRAY CODE)]);
112              
113 11   50     1157 my $field_map_ref = $meta->{field_map} ||= {};
114              
115 11   50     1133 my $fields = $meta->{fields} ||= [];
116              
117 11         1101 while(my($name, $validator) = each %{$fields_ref}){
  24         4597  
118 13         933 my $slot;
119              
120             my $validate_sub;
121              
122 13 100       1061 if($validator){
123 10 100       11351 if(Data::Util::is_regex_ref $validator){
    100          
124 2     4   14 $validate_sub = sub{ $_[0] =~ /$validator/ };
  4         92  
125             }
126             elsif(Data::Util::is_array_ref $validator){
127 5         1277 my %words;
128 5         1041 @words{@{$validator}} = ();
  5         2256  
129 5     7   3954 $validate_sub = sub{ exists $words{ $_[0] } };
  7         2343  
130             }
131             else{ # CODE reference
132 3         10 $validate_sub = $validator;
133             }
134             }
135              
136             Data::Util::install_subroutine($meta->{sclass},
137             "get_$name" => sub{
138 9 100   9   10509 if(@_ > 1){
139 1         19 Carp::croak "Too many arguments for get_$name";
140             }
141 8         38 return $slot;
142             },
143             "set_$name" => sub{
144 17 100   17   4432 if(@_ > 2){
145 1         16 Carp::croak "Cannot set multiple values for set_$name";
146             }
147 16 100       3831 if(defined $validate_sub){
148 13         3032 my $value = $_[1];
149 13 100       1761 $validate_sub->($value)
150             or Carp::croak 'Invalid value ', Data::Util::neat($value), " for set_$name";
151 10         2256 $slot = $value;
152             }
153             else{
154 3         5 $slot = $_[1];
155             }
156 13         4914 return $_[0];
157             },
158 13         2603 );
159              
160 13         1371 $field_map_ref->{$name} = \$slot;
161 13         1302 push @{$fields}, $name => $validate_sub;
  13         3733  
162             }
163 11         2555 return;
164             }
165              
166             sub add_modifier{
167 3     3 1 5 my $meta = shift;
168              
169 3         1335 require Class::Method::Modifiers::Fast;
170              
171 3         1217 Class::Method::Modifiers::Fast::_install_modifier($meta->{sclass}, @_);
172 3   100     123 push @{$meta->{modifiers} ||= []}, @_;
  3         18  
173 3         7 return;
174             }
175              
176             sub inject_base{
177 9     9 1 7378 my($meta, @components) = @_;
178              
179             # NOTE: In 5.10.0, do{unshift @ISA, @classes} may cause 'uninitialized' warnings.
180            
181 9         6006 @{$meta->{isa}} = (
  15         10179  
182 9         3827 (grep{ not $meta->{object}->isa($_) } @components),
183 9         4829 @{$meta->{isa}},
184             );
185 9         14155 return;
186             }
187              
188             sub bless{
189 3     3 1 29 my($meta, $object) = @_;
190              
191 3         23 my $newmeta = ref($meta)->initialize($object);
192              
193 3 50       17 $newmeta->add_methods( @{ $meta->{methods} } )
  3         14  
194             if exists $meta->{methods};
195              
196 3 50       13 if(exists $meta->{fields}){
197 3         5 $newmeta->add_fields(@{$meta->{fields}});
  3         14  
198              
199 3         10 my $src_map_ref = $meta->{field_map};
200 3         7 my $new_map_ref = $newmeta->{field_map};
201 3         6 while(my($key, $val_ref) = each %{$src_map_ref}){
  5         25  
202 2         3 ${$new_map_ref->{$key}} = ${$val_ref};
  2         7  
  2         12  
203             }
204             }
205              
206 2         13 $newmeta->inject_base(@{$meta->{isa}}[0 .. $#{$meta->{isa}}-2])
  2         7  
  3         58  
207 3 100       6 if @{$meta->{isa}} > 2; # other than Monadic::Object and its original class
208              
209 3         18 return $object;
210             }
211              
212             sub DESTROY{
213 30     30   15659 my($meta) = @_;
214 30         5776 my $original_stash = Data::Util::get_stash($meta->{class});
215              
216 30         5366 my $sclass_stashgv = delete $original_stash->{$meta->{id} . '::'};
217              
218 30         4219 @{$meta->{isa}} = ();
  30         7968  
219 30         4728 %{$sclass_stashgv} = ();
  30         7957  
220              
221 30         14426 return;
222             }
223              
224             package Class::Monadic::Object;
225              
226             sub STORABLE_freeze{
227 2     2   105 my($object, $cloning) = @_;
228              
229 2 100       107 return if $cloning;
230 1         43 Carp::croak sprintf 'Cannot serialize monadic object (%s)', Data::Util::neat($object);
231             }
232              
233             package Class::Monadic::Object::Clonable;
234             our @ISA = qw(Class::Monadic::Object);
235              
236             sub clone{
237 2     2   27 my($object) = @_;
238 2         6 my $meta = $Meta{$object};
239              
240 2 50       19 my $clone = $meta->{class}->can('clone') or Carp::croak(qq{Cannot find "clone" method for $meta->{class}});
241 2         11 return $meta->bless( $clone->($object) );
242             }
243              
244              
245             1;
246             __END__