File Coverage

blib/lib/exact/class.pm
Criterion Covered Total %
statement 139 141 98.5
branch 53 58 91.3
condition 9 18 50.0
subroutine 24 25 96.0
pod 4 4 100.0
total 229 246 93.0


line stmt bran cond sub pod time code
1             package exact::class;
2             # ABSTRACT: Simple class interface extension for exact
3              
4 16     16   1414986 use 5.014;
  16         1327  
5 15     15   61 use exact;
  15         1011  
  8         106  
6 8     8   9268 use Role::Tiny ();
  8         26805  
  8         182  
7 8     8   86 use Scalar::Util ();
  8         19  
  8         128  
8 8     8   4296 use Class::Method::Modifiers ();
  8         13646  
  8         2202  
9              
10             our $VERSION = '1.17'; # VERSION
11              
12             my $store;
13              
14             sub import {
15 29     29   19683 my ( $self, $caller ) = @_;
16              
17 29 100       85 if ($caller) {
18 26         84 exact->late_parent;
19             }
20             else {
21 3   33     26 $caller //= caller();
22 3 100       24 exact->add_isa( $self, $caller ) if ( $self eq 'exact::class');
23             }
24              
25 29         165 $store->{struc}{$caller} = {};
26              
27 8     8   46 eval qq{
  8     6   2170  
  6         19  
  6         956  
  29         3228  
28             package $caller {
29             no feature 'class';
30             use Class::Method::Modifiers;
31             };
32             };
33              
34 29         336 exact->monkey_patch( $caller, $_, \&$_ ) for ( qw( has class_has with ) );
35             }
36              
37       0     sub DESTROY {}
38              
39             sub ____parents {
40 89     89   143 my ($namespace) = @_;
41 8     8   62 no strict 'refs';
  8         22  
  8         13898  
42 89         109 my @parents = @{ $namespace . '::ISA' };
  89         326  
43 89         245 return @parents, map { ____parents($_) } @parents;
  51         111  
44             }
45              
46             sub ____install {
47 115     115   206 my ( $self, $namespace, $input ) = @_;
48              
49 115 100       348 if ( ref $store->{struc}{$namespace} eq 'HASH' ) {
50 68         95 my @has_names = keys %{ $store->{struc}{$namespace}->{has} };
  68         234  
51              
52 68         121 for my $class_has_name (
53             grep {
54 239         305 my $name = $_;
55 239         305 not grep { $_ eq $name } @has_names;
  1517         2276  
56 68         170 } keys %{ $store->{struc}{$namespace}->{name} }
57             ) {
58 68 100       153 $self->$class_has_name( $input->{$class_has_name} ) if ( exists $input->{$class_has_name} );
59             }
60              
61 68         120 for my $has_name (@has_names) {
62 171 100       415 if ( exists $input->{$has_name} ) {
    100          
63 10         25 $self->attr( $has_name, $input->{$has_name} );
64             }
65             elsif ( exists $store->{struc}{$namespace}->{value}{$has_name} ) {
66 111         307 $self->attr( $has_name, $store->{struc}{$namespace}->{value}{$has_name} );
67             }
68             else {
69 50         90 $self->attr($has_name);
70             }
71             }
72             }
73             }
74              
75             sub new {
76 38     38 1 3480 my $class = shift;
77 38 100       109 my $input = @_ ? @_ > 1 ? {@_} : { %{ $_[0] } } : {};
  2 100       6  
78 38   33     208 my $self = bless( { %$input }, ref $class || $class );
79              
80 38         110 for my $namespace ( reverse ( ref $self, ____parents( ref $self ) ) ) {
81 89 100       261 if ( ref $store->{roles}{$namespace} eq 'ARRAY' ) {
82 26         39 for my $role ( @{ $store->{roles}->{$namespace} } ) {
  26         59  
83 26         47 ____install( $self, $role, $input );
84             }
85             }
86              
87 89         161 ____install( $self, $namespace, $input );
88             }
89              
90 38         177 return $self;
91             }
92              
93             sub tap {
94 4     4 1 10 my ( $self, $cb ) = ( shift, shift );
95 4         14 $_->$cb(@_) for $self;
96 4         11 return $self;
97             }
98              
99             sub attr {
100 179     179 1 7940 my ( $self, $attrs, $value ) = @_;
101              
102 179   66     654 my $set = {
103             attrs => $attrs,
104             caller => ref($self) || $self,
105             set_has => 1,
106             self => $self,
107             obj_accessor => 1,
108             };
109              
110 179 100       434 $set->{value} = $value if ( @_ > 2 );
111 179         302 return ____attrs($set);
112             }
113              
114             sub class_has {
115 12     12   146 my ( $attrs, $value ) = @_;
116              
117 12         38 my $set = {
118             attrs => $attrs,
119             caller => scalar( caller() ),
120             };
121              
122 12 50       34 $set->{value} = $value if ( @_ > 1 );
123 12         27 ____attrs($set);
124 12         53 return;
125             }
126              
127             sub has {
128 45     45   1638 my ( $attrs, $value ) = @_;
129              
130 45         175 my $set = {
131             attrs => $attrs,
132             caller => scalar( caller() ),
133             set_has => 1,
134             };
135              
136 45 100       134 $set->{value} = $value if ( @_ > 1 );
137 45         111 ____attrs($set);
138 45         252 return;
139             }
140              
141             sub ____attrs {
142 265     265   402 for my $set (@_) {
143 265 100       522 for my $name ( ( ref $set->{attrs} ) ? @{ $set->{attrs} } : $set->{attrs} ) {
  6         14  
144             my $accessor = ( $set->{obj_accessor} )
145             ? sub {
146 264     264   22631 my ( $self, $value ) = @_;
147              
148 264 100       486 if ( @_ > 1 ) {
149 151         572 $self->{$name} = $value;
150 151         360 return $self;
151             }
152             else {
153 4         16 return ${ $self->{$name} } if (
154             ref $self->{$name} eq 'REF' and
155 113 100 66     314 ref ${ $self->{$name} } eq 'CODE'
  4         16  
156             );
157              
158 109 100       276 $self->{$name} = $self->{$name}->($self) if ( ref $self->{$name} eq 'CODE' );
159 109         441 return $self->{$name};
160             }
161             }
162             : sub {
163 85     85   560 my ( $self, $value ) = @_;
164              
165 85 100       162 if ( @_ > 1 ) {
166 16         38 $store->{struc}{ $set->{caller} }->{value}{$name} = $value;
167 16         68 return $self;
168             }
169             else {
170 0         0 return ${ $store->{struc}{ $set->{caller} }->{value}{$name} } if (
171             ref $store->{struc}{ $set->{caller} }->{value}{$name} eq 'REF' and
172 69 50 33     197 ref ${ $store->{struc}{ $set->{caller} }->{value}{$name} } eq 'CODE'
  0         0  
173             );
174              
175             $store->{struc}{ $set->{caller} }->{value}{$name} =
176             $store->{struc}{ $set->{caller} }->{value}{$name}->($self)
177 69 50       138 if ( ref $store->{struc}{ $set->{caller} }->{value}{$name} eq 'CODE' );
178 69         263 return $store->{struc}{ $set->{caller} }->{value}{$name};
179             }
180 277 100       1067 };
181              
182             {
183 8     8   69 no strict 'refs';
  8         20  
  8         290  
  277         454  
184 8     8   49 no warnings 'redefine';
  8         15  
  8         8667  
185 277         337 *{ $set->{caller} . '::' . $name } = $accessor;
  277         1495  
186             }
187              
188 277 100       536 if ( ref $set->{self} ) {
189 179 100       536 $set->{self}->$name( $set->{value} ) if ( exists $set->{value} );
190             }
191             else {
192 98 100       273 $store->{struc}{ $set->{caller} }->{has}{$name} = 1 if ( $set->{set_has} );
193 98         202 $store->{struc}{ $set->{caller} }->{name}{$name} = 1;
194 98 100       320 $store->{struc}{ $set->{caller} }->{value}{$name} = $set->{value} if ( exists $set->{value} );
195             }
196             }
197             }
198              
199 265         627 return;
200             }
201              
202             sub ____role_attrs {
203 16     16   42 my ( $caller, $roles, $object ) = @_;
204              
205 16         34 for my $role (@$roles) {
206 21         37 for my $name (
207 21         90 keys %{ $store->{struc}{$role}{name} }
208             ) {
209 29         73 my $set = {
210             attrs => $name,
211             caller => $caller,
212             };
213              
214 29 100       81 if ( $store->{struc}{$role}{has}{$name} ) {
215 18         32 $set->{self} = $object;
216 18         32 $set->{obj_accessor} = 1;
217 18         31 $set->{set_has} = 1;
218             }
219              
220             $set->{value} = $store->{struc}{$role}{value}{$name}
221 29 100       79 if ( exists $store->{struc}{$role}{value}{$name} );
222              
223 29         56 ____attrs($set);
224             }
225             }
226              
227 16         27 return;
228             }
229              
230             sub with {
231 11     11   138 my $caller = scalar(caller);
232 11         17 push( @{ $store->{roles}->{$caller} }, @_ );
  11         44  
233              
234             try {
235             Role::Tiny->apply_roles_to_package( $caller, $_ ) for @_;
236             }
237 11         34 catch ($e) {
238             $e =~ s/\s+at\s.+\sline\s\d+\.\s*$//g;
239             croak $e;
240             }
241              
242 11         3621 ____role_attrs( $caller, [@_] );
243 11         32 return;
244             }
245              
246             sub with_roles {
247 5     5 1 551 my ( $self, @roles ) = @_;
248 5         8 my $object;
249              
250 5 100       24 unless ( my $class = Scalar::Util::blessed($self) ) {
251             $object = Role::Tiny->create_class_with_roles(
252             $self,
253 1 50       3 map { /^\+(.+)$/ ? "${self}::Role::$1" : $_ } @roles
  1         12  
254             );
255             }
256             else {
257             $object = Role::Tiny->apply_roles_to_object(
258             $self,
259 4 50       7 map { /^\+(.+)$/ ? "${class}::Role::$1" : $_ } @roles
  4         26  
260             );
261             }
262              
263 5   66     1515 ____role_attrs( Scalar::Util::blessed($object) || $object, [@_], $object );
264 5         25 return $object;
265             }
266              
267             1;
268              
269             __END__