File Coverage

blib/lib/Minions.pm
Criterion Covered Total %
statement 350 398 87.9
branch 115 156 73.7
condition 40 57 70.1
subroutine 68 79 86.0
pod 1 2 50.0
total 574 692 82.9


line stmt bran cond sub pod time code
1             package Minions;
2              
3 48     48   3377031 use strict;
  48         109  
  48         1152  
4 48     48   913 use 5.008_005;
  48         155  
5 48     48   213 use Carp;
  48         85  
  48         2452  
6 48     48   14571 use Hash::Util qw( lock_keys );
  48         99717  
  48         251  
7 48     48   17825 use List::MoreUtils qw( all );
  48         312249  
  48         453  
8 48     48   41511 use Module::Runtime qw( require_module );
  48         59702  
  48         253  
9 48     48   16506 use Params::Validate qw(:all);
  48         224867  
  48         7650  
10 48     48   11686 use Package::Stash;
  48         51647  
  48         1247  
11 48     48   11005 use Sub::Name;
  48         16530  
  48         2636  
12              
13             use Exception::Class (
14 48         388 'Minions::Error::AssertionFailure' => { alias => 'assert_failed' },
15             'Minions::Error::InterfaceMismatch',
16             'Minions::Error::MethodDeclaration',
17             'Minions::Error::RoleConflict',
18 48     48   4373 );
  48         110015  
19 48     48   47120 use Minions::_Guts;
  48         110  
  48         187380  
20              
21             our $VERSION = '1.000001';
22             $VERSION = eval $VERSION;
23              
24             my $Class_count = 0;
25             my %Bound_implementation_of;
26             my %Interface_for;
27             my %Util_class;
28              
29             sub import {
30 23     23   1987 my ($class, %arg) = @_;
31              
32 23 100       99 if ( my $bindings = $arg{bind} ) {
    100          
33              
34 4         13 foreach my $class ( keys %$bindings ) {
35 4         96 $Bound_implementation_of{$class} = $bindings->{$class};
36             }
37             }
38             elsif ( my $methods = $arg{declare_interface} ) {
39 1         3 my $caller_pkg = (caller)[0];
40 1         17 $Interface_for{$caller_pkg} = $methods;
41             }
42             else {
43 18         64 $class->minionize(\%arg);
44             }
45             }
46              
47             sub minionize {
48 53     53 1 3703 my (undef, $spec) = @_;
49              
50 53         94 my $cls_stash;
51 53 100       192 if ( ! $spec->{name} ) {
52 51         199 my $caller_pkg = (caller)[0];
53              
54 51 100       1025 if ( $caller_pkg eq __PACKAGE__ ) {
55 18         72 $caller_pkg = (caller 1)[0];
56             }
57 51         981 $cls_stash = Package::Stash->new($caller_pkg);
58 51 100       177 $spec = { %$spec, %{ $cls_stash->get_symbol('%__meta__') || {} } };
  51         1101  
59 51         180 $spec->{name} = $caller_pkg;
60             }
61 53   33     163 $spec->{name} ||= "Minions::Class_${\ ++$Class_count }";
  0         0  
62              
63 53         177 my @args = %$spec;
64 53         2192 validate(@args, {
65             interface => { type => ARRAYREF | SCALAR },
66             implementation => { type => SCALAR | HASHREF },
67             construct_with => { type => HASHREF, optional => 1 },
68             class_methods => { type => HASHREF, optional => 1 },
69             build_args => { type => CODEREF, optional => 1 },
70             name => { type => SCALAR, optional => 1 },
71             no_attribute_vars => { type => BOOLEAN, optional => 1 },
72             });
73 53   66     541 $cls_stash ||= Package::Stash->new($spec->{name});
74              
75 53         102 my $obj_stash;
76              
77 53 100       156 if ( ! ref $spec->{implementation} ) {
78 49   66     271 my $pkg = $Bound_implementation_of{ $spec->{name} } || $spec->{implementation};
79             $pkg ne $spec->{name}
80 49 50       149 or confess "$spec->{name} cannot be its own implementation.";
81 49         295 my $stash = _get_stash($pkg);
82              
83 49         190 my $meta = $stash->get_symbol('%__meta__');
84             $spec->{implementation} = {
85             package => $pkg,
86             methods => $stash->get_all_symbols('CODE'),
87             has => {
88 49 100       370 %{ $meta->{has} || { } },
  49         325  
89             },
90             };
91 49         130 $spec->{roles} = $meta->{roles};
92 49         84 $spec->{traitlibs} = $meta->{traitlibs};
93 49         138 my $is_semiprivate = _interface($meta, 'semiprivate');
94              
95 49         95 foreach my $sub ( keys %{ $spec->{implementation}{methods} } ) {
  49         240  
96 60 100       221 if ( $is_semiprivate->{$sub} ) {
97 7         33 $spec->{implementation}{semiprivate}{$sub} = delete $spec->{implementation}{methods}{$sub};
98             }
99             }
100             }
101 53         408 $obj_stash = Package::Stash->new("$spec->{name}::__Minions");
102              
103 53         197 _prep_interface($spec);
104 53         152 _compose_roles($spec);
105 49         151 _compose_traitlibs($spec);
106              
107 49         388 my $private_stash = Package::Stash->new("$spec->{name}::__Private");
108 49         479 $cls_stash->add_symbol('$__Obj_pkg', $obj_stash->name);
109 49         332 $cls_stash->add_symbol('$__Private_pkg', $private_stash->name);
110 49 50       371 $cls_stash->add_symbol('%__meta__', $spec) if @_ > 0;
111              
112 49         169 _make_util_class($spec);
113 49         174 _add_class_methods($spec, $cls_stash);
114 49         184 _add_methods($spec, $obj_stash, $private_stash);
115 49         173 _check_role_requirements($spec);
116 47         233 _check_traitlib_requirements($spec);
117 47         230 _check_interface($spec);
118 46         598 return $spec->{name};
119             }
120              
121             sub utility_class {
122 67     67 0 4006 my ($class) = @_;
123              
124 67 0       202 return $Util_class{ $class }
125             or confess "Unknown class: $class";
126             }
127              
128             sub _prep_interface {
129 53     53   114 my ($spec) = @_;
130              
131 53 100       220 return if ref $spec->{interface};
132 1         1 my $count = 0;
133             {
134              
135 1 100       2 if (my $methods = $Interface_for{ $spec->{interface} }) {
  2         5  
136 1         2 $spec->{interface_name} = $spec->{interface};
137 1         3 $spec->{interface} = $methods;
138             }
139             else {
140 1 50       4 $count > 0
141             and confess "Invalid interface: $spec->{interface}";
142 1         3 require_module($spec->{interface});
143 1         7 $count++;
144 1         1 redo;
145             }
146             }
147             }
148              
149             sub _compose_roles {
150 83     83   172 my ($spec, $roles, $from_role) = @_;
151              
152 83 100       191 if ( ! $roles ) {
153 53         114 $roles = $spec->{roles};
154             }
155              
156 83   100     351 $from_role ||= {};
157              
158 83         114 for my $role ( @{ $roles } ) {
  83         206  
159              
160 30 50       84 if ( $spec->{composed_role}{$role} ) {
161 0         0 confess "Cannot compose role '$role' twice";
162             }
163             else {
164 30         63 $spec->{composed_role}{$role}++;
165             }
166              
167 30         76 my ($meta, $method) = _load_role($role);
168 30         101 $spec->{required_by_role}{$role} = $meta->{requires};
169 30   100     227 _compose_roles($spec, $meta->{roles} || [], $from_role);
170              
171 29         133 _add_role_items($spec, $from_role, $role, $meta->{has}, 'has');
172 28         87 _add_role_methods($spec, $from_role, $role, $meta, $method);
173             }
174             }
175              
176             sub _compose_traitlibs {
177 49     49   107 my ($spec, $traitlibs, $from_traitlib) = @_;
178              
179 49 50       125 if ( ! $traitlibs ) {
180 49         101 $traitlibs = $spec->{traitlibs};
181             }
182              
183 49   50     252 $from_traitlib ||= {};
184              
185 49         71 for my $traitlib ( @{ $traitlibs } ) {
  49         120  
186              
187 0 0       0 if ( $spec->{composed_traitlib}{$traitlib} ) {
188 0         0 confess "Cannot compose traitlib '$traitlib' twice";
189             }
190             else {
191 0         0 $spec->{composed_traitlib}{$traitlib}++;
192             }
193              
194 0         0 my ($meta, $method) = _load_traitlib($traitlib);
195 0         0 $spec->{required_by_traitlib}{$traitlib} = $meta->{requires};
196 0   0     0 _compose_traitlibs($spec, $meta->{traitlibs} || [], $from_traitlib);
197              
198 0         0 _add_traitlib_items($spec, $from_traitlib, $traitlib, $meta->{has}, 'has');
199 0         0 _add_traitlib_methods($spec, $from_traitlib, $traitlib, $meta, $method);
200             }
201             }
202              
203             sub _load_role {
204 31     31   58 my ($role) = @_;
205              
206 31         65 my $stash = _get_stash($role);
207 31         171 my $meta = $stash->get_symbol('%__meta__');
208             $meta->{role}
209 31 50       100 or confess "$role is not a role";
210              
211 31         149 my $method = $stash->get_all_symbols('CODE');
212 31         115 return ($meta, $method);
213             }
214              
215             sub _load_traitlib {
216 0     0   0 my ($traitlib) = @_;
217              
218 0         0 my $stash = _get_stash($traitlib);
219 0         0 my $meta = $stash->get_symbol('%__meta__');
220             $meta->{traitlib}
221 0 0       0 or confess "$traitlib is not a traitlib";
222              
223 0         0 my $method = $stash->get_all_symbols('CODE');
224 0         0 return ($meta, $method);
225             }
226              
227             sub _check_role_requirements {
228 49     49   126 my ($spec) = @_;
229              
230 49         138 _check_traitlib_requirements($spec, 'required_by_role');
231             }
232              
233             sub _check_traitlib_requirements {
234 96     96   185 my ($spec, $type) = @_;
235              
236 96   100     334 $type ||= 'required_by_traitlib';
237 96         142 my $required_by = do { my $tmp = $type; $tmp =~ s/_/ /g; $tmp };
  96         140  
  96         368  
  96         232  
238              
239 96         221 foreach my $traitlib ( keys %{ $spec->{$type} } ) {
  96         381  
240              
241 21         42 my $required = $spec->{$type}{$traitlib};
242              
243 21         31 foreach my $name ( @{ $required->{methods} } ) {
  21         62  
244              
245 3 100 100     17 unless ( defined $spec->{implementation}{methods}{$name}
246             || defined $spec->{implementation}{semiprivate}{$name}
247             ) {
248 1         18 confess "Method '$name', $required_by $traitlib, is not implemented.";
249             }
250             }
251 20         32 foreach my $name ( @{ $required->{attributes} } ) {
  20         54  
252 2 100       31 defined $spec->{implementation}{has}{$name}
253             or confess "Attribute '$name', $required_by $traitlib, is not defined.";
254             }
255             }
256             }
257              
258             sub _check_interface {
259 47     47   114 my ($spec) = @_;
260 47         88 my $count = 0;
261 47         91 foreach my $method ( @{ $spec->{interface} } ) {
  47         125  
262 106 100       269 defined $spec->{implementation}{methods}{$method}
263             or confess "Interface method '$method' is not implemented.";
264 105         168 ++$count;
265             }
266 46 50       142 $count > 0 or confess "Cannot have an empty interface.";
267             }
268              
269             sub _get_stash {
270 81     81   227 my $pkg = shift;
271              
272 81         507 my $stash = Package::Stash->new($pkg); # allow for inlined pkg
273              
274 81 100       804 if ( ! $stash->has_symbol('%__meta__') ) {
275 29         140 require_module($pkg);
276 29         475 $stash = Package::Stash->new($pkg);
277             }
278 81 50       537 if ( ! $stash->has_symbol('%__meta__') ) {
279 0         0 confess "Package $pkg has no %__meta__";
280             }
281 81         212 return $stash;
282             }
283              
284             sub _add_role_items {
285 29     29   88 my ($spec, $from_role, $role, $item, $type) = @_;
286              
287 29         82 for my $name ( keys %$item ) {
288 11 100       32 if (my $other_role = $from_role->{$name}) {
289 1         2 _raise_role_conflict($name, $role, $other_role);
290             }
291             else{
292 10 100       35 if ( ! $spec->{implementation}{$type}{$name} ) {
293 7         18 $spec->{implementation}{$type}{$name} = $item->{$name};
294 7         21 $from_role->{$name} = $role;
295             }
296             }
297             }
298             }
299              
300             sub _add_traitlib_items {
301 0     0   0 my ($spec, $from_traitlib, $traitlib, $item, $type) = @_;
302              
303 0         0 my $wanted = $spec->{implementation}{traitlibs}{$traitlib}{attributes};
304 0         0 for my $name ( @{$wanted} ) {
  0         0  
305              
306 0 0       0 if (my $other_traitlib = $from_traitlib->{$name}) {
307 0         0 _raise_role_conflict($name, $traitlib, $other_traitlib);
308             }
309 0 0       0 if (exists $item->{$name}) {
310 0 0       0 if ( ! $spec->{implementation}{$type}{$name} ) {
311 0         0 $spec->{implementation}{$type}{$name} = $item->{$name};
312 0         0 $from_traitlib->{$name} = $traitlib;
313             }
314             }
315             else {
316 0         0 confess "Attribute $name not available via traitlib $traitlib";
317             }
318             }
319             }
320              
321             sub _add_role_methods {
322 28     28   63 my ($spec, $from_role, $role, $role_meta, $code_for) = @_;
323              
324 28         62 my $in_class_interface = _interface($spec);
325 28         92 my $in_role_interface = _interface($role_meta);
326 28         65 my $is_semiprivate = _interface($role_meta, 'semiprivate');
327              
328 28 100   109   211 all { defined $in_class_interface->{$_} } keys %$in_role_interface
  109         443  
329             or Minions::Error::InterfaceMismatch->throw(
330             error => "Interfaces do not match: Class => $spec->{name}, Role => $role"
331             );
332              
333 27         212 for my $name ( keys %$code_for ) {
334 42 100 66     180 if ( $in_role_interface->{$name}
    50          
335             || $in_class_interface->{$name}
336             ) {
337 40 100       132 if (my $other_role = $from_role->{method}{$name}) {
338 2         6 _raise_role_conflict($name, $role, $other_role);
339             }
340 38 100       107 if ( ! $spec->{implementation}{methods}{$name} ) {
341 36         61 $spec->{implementation}{methods}{$name} = $code_for->{$name};
342 36         145 $from_role->{method}{$name} = $role;
343             }
344             }
345             elsif ( $is_semiprivate->{$name} ) {
346 2 50       6 if (my $other_role = $from_role->{semiprivate}{$name}) {
347 0         0 _raise_role_conflict($name, $role, $other_role);
348             }
349 2 100       6 if ( ! $spec->{implementation}{semiprivate}{$name} ) {
350 1         2 $spec->{implementation}{semiprivate}{$name} = $code_for->{$name};
351 1         5 $from_role->{semiprivate}{$name} = $role;
352             }
353             }
354             }
355             }
356              
357             sub _raise_role_conflict {
358 3     3   7 my ($name, $role, $other_role) = @_;
359              
360 3         41 Minions::Error::RoleConflict->throw(
361             error => "Cannot have '$name' in both $role and $other_role"
362             );
363             }
364              
365             sub _get_object_maker {
366              
367             sub {
368 65     65   197 my ($utility_class, $init) = @_;
        65      
        10      
369              
370 65         174 my $class = $utility_class->main_class;
371              
372 65         549 my $stash = Package::Stash->new($class);
373              
374 65         490 my $spec = $stash->get_symbol('%__meta__');
375 65         240 my $pkg_key = Minions::_Guts::obfu_name('', $spec);
376             my %obj = (
377 65         124 $pkg_key => ${ $stash->get_symbol('$__Private_pkg') },
  65         379  
378             );
379              
380 65         159 while ( my ($attr, $meta) = each %{ $spec->{implementation}{has} } ) {
  150         664  
381 85         202 my $obfu_name = Minions::_Guts::obfu_name($attr, $spec);
382             $obj{$obfu_name} = $init->{$attr}
383             ? $init->{$attr}
384             : (ref $meta->{default} eq 'CODE'
385             ? $meta->{default}->()
386 85 100       425 : $meta->{default});
    100          
387             }
388              
389 65         126 bless \ %obj => ${ $stash->get_symbol('$__Obj_pkg') };
  65         326  
390 65         278 lock_keys(%obj);
391 65         1517 return \ %obj;
392 49     49   316 };
393             }
394              
395             sub _add_class_methods {
396 49     49   130 my ($spec, $stash) = @_;
397              
398 49   66     817 $spec->{class_methods} ||= $stash->get_all_symbols('CODE');
399 49         155 _add_default_constructor($spec);
400              
401 49         94 foreach my $sub ( keys %{ $spec->{class_methods} } ) {
  49         212  
402 442         1633 $stash->add_symbol("&$sub", $spec->{class_methods}{$sub});
403 442         1736 subname "$spec->{name}::$sub", $spec->{class_methods}{$sub};
404             }
405             }
406              
407             sub _make_util_class {
408 49     49   103 my ($spec) = @_;
409              
410 49         311 my $stash = Package::Stash->new("$spec->{name}::__Util");
411 49         218 $Util_class{ $spec->{name} } = $stash->name;
412              
413 49         147 my %method = (
414             new_object => _get_object_maker(),
415             );
416              
417 49     65   189 $method{main_class} = sub { $spec->{name} };
  65     65   150  
        10      
418              
419 49         225 my $obfu_pkg = Minions::_Guts::obfu_name('', $spec);
420             $method{build} = sub {
421 47     47   110 my (undef, $obj, $arg) = @_;
        47      
        10      
422 47 100       491 if ( my $builder = $obj->{$obfu_pkg}->can('BUILD') ) {
423 9         33 $builder->($obj->{$obfu_pkg}, $obj, $arg);
424             }
425 49         237 };
426              
427             $method{assert} = sub {
428 34     34   98 my (undef, $slot, $val) = @_;
        34      
        4      
429              
430 34 50       217 return unless exists $spec->{construct_with}{$slot};
431              
432 34         60 my $meta = $spec->{construct_with}{$slot};
433              
434 34 100       59 for my $desc ( keys %{ $meta->{assert} || {} } ) {
  34         162  
435 25         46 my $code = $meta->{assert}{$desc};
436 25 100       87 $code->($val)
437             or assert_failed error => "Parameter '$slot' failed check '$desc'";
438             }
439 49         242 };
440              
441 49         402 my $class_var_stash = Package::Stash->new("$spec->{name}::__ClassVar");
442              
443             $method{get_var} = sub {
444 0     0   0 my ($class, $name) = @_;
        0      
        0      
445 0         0 $class_var_stash->get_symbol($name);
446 49         269 };
447              
448             $method{set_var} = sub {
449 0     0   0 my ($class, $name, $val) = @_;
        0      
        0      
450 0         0 $class_var_stash->add_symbol($name, $val);
451 49         213 };
452              
453 49         170 foreach my $sub ( keys %method ) {
454 294         1746 $stash->add_symbol("&$sub", $method{$sub});
455 294         1909 subname $stash->name."::$sub", $method{$sub};
456             }
457             }
458              
459             sub _add_default_constructor {
460 49     49   108 my ($spec) = @_;
461              
462 49 100       236 if ( ! exists $spec->{class_methods}{new} ) {
463             $spec->{class_methods}{new} = sub {
464 64     64   14642 my $class = shift;
        59      
        10      
465 64         103 my ($arg);
466              
467 64 100       290 if ( scalar @_ == 1 ) {
    100          
468 5         8 $arg = shift;
469             }
470             elsif ( scalar @_ > 1 ) {
471 27         67 $arg = { @_ };
472             }
473 64 100       240 if (my @unknown = grep { ! exists $spec->{construct_with}{$_} } keys %$arg) {
  34         177  
474 1         16 confess "Unknown args: [@unknown]";
475             }
476              
477 63         199 my $utility_class = utility_class($class);
478 63         223 my $obj = $utility_class->new_object;
479 63         112 for my $name ( keys %{ $spec->{construct_with} } ) {
  63         211  
480              
481 45 100 100     249 if ( ! $spec->{construct_with}{$name}{optional} && ! defined $arg->{$name} ) {
482 7         118 confess "Param '$name' was not provided.";
483             }
484 38 100       105 if ( defined $arg->{$name} ) {
485 30         83 $utility_class->assert($name, $arg->{$name});
486             }
487              
488 48         146 my ($attr, $dup) = grep { $spec->{implementation}{has}{$_}{init_arg} eq $name }
489 29         162 keys %{ $spec->{implementation}{has} };
  29         88  
490 29 50       89 if ( $dup ) {
491 0         0 confess "Cannot have same init_arg '$name' for attributes '$attr' and '$dup'";
492             }
493 29 100       94 if ( $attr ) {
494 20         68 _copy_assertions($spec, $name, $attr);
495 20         45 my $sub = $spec->{implementation}{has}{$attr}{map_init_arg};
496 20         63 my $obfu_name = Minions::_Guts::obfu_name($attr, $spec) ;
497 20 100       100 $obj->{$obfu_name} = $sub ? $sub->($arg->{$name}) : $arg->{$name};
498             }
499             }
500              
501 47         207 $utility_class->build($obj, $arg);
502 46         234 return $obj;
503 47         315 };
504              
505 47   100     239 my $build_args = $spec->{build_args} || $spec->{class_methods}{BUILDARGS};
506 47 100       142 if ( $build_args ) {
507 3         4 my $prev_new = $spec->{class_methods}{new};
508              
509             $spec->{class_methods}{new} = sub {
510 5     5   1536 my $class = shift;
        41      
511 5         16 $prev_new->($class, $build_args->($class, @_));
512 3         11 };
513             }
514             }
515             }
516              
517             sub _copy_assertions {
518 20     20   49 my ($spec, $name, $attr) = @_;
519              
520 20         42 my $meta = $spec->{construct_with}{$name};
521              
522 20 100       34 for my $desc ( keys %{ $meta->{assert} || {} } ) {
  20         101  
523 14 100       48 next if exists $spec->{implementation}{has}{$attr}{assert}{$desc};
524              
525 11         35 $spec->{implementation}{has}{$attr}{assert}{$desc} = $meta->{assert}{$desc};
526             }
527             }
528              
529             sub _add_methods {
530 49     49   116 my ($spec, $stash, $private_stash) = @_;
531              
532 49         119 my $in_interface = _interface($spec);
533              
534             $spec->{implementation}{semiprivate}{ASSERT} = sub {
535 2     2   22 my (undef, $slot, $val) = @_;
        0      
        2      
536              
537 2 50       5 return unless exists $spec->{implementation}{has}{$slot};
538              
539 2         4 my $meta = $spec->{implementation}{has}{$slot};
540              
541 2 50       2 for my $desc ( keys %{ $meta->{assert} || {} } ) {
  2         8  
542 2         3 my $code = $meta->{assert}{$desc};
543 2 100       8 $code->($val)
544             or assert_failed error => "Attribute '$slot' failed check '$desc'";
545             }
546 49         261 };
547             $spec->{implementation}{methods}{DOES} = sub {
548 12     12   3171 my ($self, $r) = @_;
        0      
        12      
549              
550 12 100       26 if ( ! $r ) {
551             my @items = (( $spec->{interface_name} ? $spec->{interface_name} : () ),
552 1 50       3 $spec->{name}, sort keys %{ $spec->{composed_role} });
  1         8  
553 1 50       3 return unless defined wantarray;
554 1 50       11 return wantarray ? @items : \@items;
555             }
556              
557             return $r eq $spec->{interface_name}
558             || $spec->{name} eq $r
559 11   66     105 || $spec->{composed_role}{$r}
560             || $self->isa($r);
561 49         249 };
562             $spec->{implementation}{methods}{can} = sub {
563 17     17   1165 my ($self, $f) = @_;
        17      
        12      
564              
565 17 50       37 if ( ! $f ) {
566 0         0 my @items = sort @{ $spec->{interface} };
  0         0  
567 0 0       0 return unless defined wantarray;
568 0 0       0 return wantarray ? @items : \@items;
569             }
570 17         51 return UNIVERSAL::can($self, $f);
571 49         201 };
572 49         172 _add_autoload($spec, $stash);
573              
574 49         91 while ( my ($name, $meta) = each %{ $spec->{implementation}{has} } ) {
  99         430  
575              
576 50 50 66     336 if ( ! $spec->{implementation}{methods}{$name}
      66        
577             && $meta->{reader}
578             && $in_interface->{$name} ) {
579              
580 7 50       26 my $name = $meta->{reader} == 1 ? $name : $meta->{reader};
581 7         24 my $obfu_name = Minions::_Guts::obfu_name($name, $spec);
582 7     9   30 $spec->{implementation}{methods}{$name} = sub { $_[0]->{$obfu_name} };
  9     24   64  
583             }
584              
585 50 0 66     253 if ( ! $spec->{implementation}{methods}{$name}
      33        
586             && $meta->{writer}
587             && $in_interface->{$name} ) {
588              
589 0         0 my $name = $meta->{writer};
590 0         0 my $obfu_pkg = Minions::_Guts::obfu_name('', $spec);
591             $spec->{implementation}{methods}{$name} = sub {
592 0     5   0 my ($self, $new_val) = @_;
593              
594 0         0 $self->{$obfu_pkg}->ASSERT($name, $new_val);
595 0         0 $self->{ Minions::_Guts::obfu_name($name, $spec) } = $new_val;
596 0         0 return $self;
597 0         0 };
598             }
599 50         154 _add_delegates($spec, $meta, $name);
600             }
601              
602 49         93 while ( my ($name, $sub) = each %{ $spec->{implementation}{methods} } ) {
  306         911  
603 257 100       504 next unless $in_interface->{$name};
604 255         2323 $stash->add_symbol("&$name", subname $stash->name."::$name" => $sub);
605             }
606 49         99 while ( my ($name, $sub) = each %{ $spec->{implementation}{semiprivate} } ) {
  106         437  
607 57         749 $private_stash->add_symbol("&$name", subname $private_stash->name."::$name" => $sub);
608             }
609             }
610              
611             sub _add_autoload {
612 49     49   113 my ($spec, $stash) = @_;
613              
614             $spec->{implementation}{methods}{AUTOLOAD} = sub {
615 67     67   73632 my $self = shift;
        11      
        67      
616              
617 67         240 my $caller_sub = (caller 1)[3];
618 67         1221 my $caller_pkg = $caller_sub;
619 67         185 $caller_pkg =~ s/::[^:]+$//;
620              
621 67         101 my $called = ${ $stash->get_symbol('$AUTOLOAD') };
  67         426  
622 67         373 $called =~ s/.+:://;
623              
624 67 100 66     431 if( exists $spec->{implementation}{semiprivate}{$called}
    100          
625             && $caller_pkg eq ref $self
626             ) {
627 1         4 my $stash = _get_stash($spec->{implementation}{package});
628 1         2 my $sp_var = ${ $stash->get_symbol('$__') };
  1         6  
629 1         15 return $self->{$sp_var}->$called($self, @_);
630             }
631             elsif( $called eq 'DESTROY' ) {
632 62         1648 return;
633             }
634             else {
635 4         78 croak sprintf(q{Can't locate object method "%s" via package "%s"},
636             $called, ref $self);
637             }
638 49         227 };
639             }
640              
641             sub _add_delegates {
642 50     50   116 my ($spec, $meta, $name) = @_;
643              
644 50 100       184 if ( $meta->{handles} ) {
645 4         4 my $method;
646 4         9 my $target_method = {};
647 4 100       21 if ( ref $meta->{handles} eq 'ARRAY' ) {
    100          
    50          
648 2         2 $method = { map { $_ => 1 } @{ $meta->{handles} } };
  6         11  
  2         5  
649             }
650             elsif( ref $meta->{handles} eq 'HASH' ) {
651 1         2 $method = $meta->{handles};
652 1         2 $target_method = $method;
653             }
654             elsif( ! ref $meta->{handles} ) {
655 1         2 (undef, $method) = _load_role($meta->{handles});
656             }
657 4         10 my $in_interface = _interface($spec);
658 4         13 my $obfu_name = Minions::_Guts::obfu_name($name, $spec);
659              
660 4         7 foreach my $meth ( keys %{ $method } ) {
  4         12  
661 14 50       28 if ( defined $spec->{implementation}{methods}{$meth} ) {
662 0         0 confess "Cannot override implemented method '$meth' with a delegated method";
663             }
664             else {
665 14   66     34 my $target = $target_method->{$meth} || $meth;
666             $spec->{implementation}{methods}{$meth} =
667             $in_interface->{$meth}
668 24     24   7264 ? sub { shift->{$obfu_name}->$target(@_) }
        12      
        4      
669 14 50   0   58 : sub { shift; shift->{$obfu_name}->$target(@_) };
  0         0  
  0         0  
670             }
671             }
672             }
673             }
674              
675             sub _interface {
676 186     186   341 my ($spec, $type) = @_;
677              
678 186   100     581 $type ||= 'interface';
679 186         574 my %must_allow = (
680             interface => [qw( AUTOLOAD can DOES DESTROY )],
681             semiprivate => [qw( BUILD )],
682             );
683 186         234 return { map { $_ => 1 } @{ $spec->{$type} }, @{ $must_allow{$type} } };
  713         1572  
  186         351  
  186         329  
684             }
685              
686             1;
687             __END__