File Coverage

blib/lib/Jojo/Role/Tiny.pm
Criterion Covered Total %
statement 263 288 91.3
branch 74 104 71.1
condition 35 68 51.4
subroutine 36 40 90.0
pod 0 11 0.0
total 408 511 79.8


line stmt bran cond sub pod time code
1             package Jojo::Role::Tiny;
2              
3 150     150   213 sub _getglob { \*{$_[0]} }
  150         1455  
4 348     348   2800 sub _getstash { \%{"$_[0]::"} }
  348         1522  
5              
6 17     17   69642 use strict;
  17         45  
  17         536  
7 17     17   93 use warnings;
  17         32  
  17         1999  
8              
9             our $VERSION = '2.000006';
10             $VERSION =~ tr/_//d;
11              
12             # Aliasing of Role::Tiny symbols
13             BEGIN {
14 17     17   77 *INFO = \%Role::Tiny::INFO;
15 17         36 *APPLIED_TO = \%Role::Tiny::APPLIED_TO;
16 17         36 *COMPOSED = \%Role::Tiny::COMPOSED;
17 17         40 *COMPOSITE_INFO = \%Role::Tiny::COMPOSITE_INFO;
18 17         3537 *ON_ROLE_CREATE = \@Role::Tiny::ON_ROLE_CREATE;
19             }
20              
21             our %INFO;
22             our %APPLIED_TO;
23             our %COMPOSED;
24             our %COMPOSITE_INFO;
25             our @ON_ROLE_CREATE;
26              
27             # Module state workaround totally stolen from Zefram's Module::Runtime.
28              
29             BEGIN {
30 17 50   17   184 *_WORK_AROUND_BROKEN_MODULE_STATE = "$]" < 5.009 ? sub(){1} : sub(){0};
31             *_WORK_AROUND_HINT_LEAKAGE
32             = "$]" < 5.011 && !("$]" >= 5.009004 && "$]" < 5.010001)
33 17 50 33     301 ? sub(){1} : sub(){0};
34 17 50       803 *_MRO_MODULE = "$]" < 5.010 ? sub(){"MRO/Compat.pm"} : sub(){"mro.pm"};
35             }
36              
37             sub croak {
38 6     6 0 42 require Carp;
39 17     17   160 no warnings 'redefine';
  17         66  
  17         37567  
40 6         43 *croak = \&Carp::croak;
41 6         1325 goto &Carp::croak;
42             }
43              
44             sub Jojo::Role::Tiny::__GUARD__::DESTROY {
45 0 0   0   0 delete $INC{$_[0]->[0]} if @{$_[0]};
  0         0  
46             }
47              
48             sub _load_module {
49 140     140   273 my ($module) = @_;
50 140         573 (my $file = "$module.pm") =~ s{::}{/}g;
51             return 1
52 140 100       402 if $INC{$file};
53              
54             # can't just ->can('can') because a sub-package Foo::Bar::Baz
55             # creates a 'Baz::' key in Foo::Bar's symbol table
56             return 1
57 132 100       175 if grep !/::\z/, keys %{_getstash($module)};
  132         255  
58 1         3 my $guard = _WORK_AROUND_BROKEN_MODULE_STATE
59             && bless([ $file ], 'Jojo::Role::Tiny::__GUARD__');
60 1         2 local %^H if _WORK_AROUND_HINT_LEAKAGE;
61 1         243 require $file;
62 0         0 pop @$guard if _WORK_AROUND_BROKEN_MODULE_STATE;
63 0         0 return 1;
64             }
65              
66             sub import {
67 1     1   11 my $target = caller;
68 1         2 my $me = shift;
69 1         5 strict->import;
70 1         10 warnings->import;
71 1         3 $me->_install_subs($target);
72 1         5 $me->make_role($target);
73             }
74              
75             sub make_role {
76 51     51 0 200 my ($me, $target) = @_;
77 51 50       175 return if $me->is_role($target); # already exported into this package
78 51         178 $INFO{$target}{is_role} = 1;
79             # get symbol table reference
80 51         161 my $stash = _getstash($target);
81             # grab all *non-constant* (stash slot is not a scalarref) subs present
82             # in the symbol table and store their refaddrs (no need to forcibly
83             # inflate constant subs into real subs) with a map to the coderefs in
84             # case of copying or re-use
85 51 50 66     929 my @not_methods = map +(ref $_ eq 'CODE' ? $_ : ref $_ ? () : *$_{CODE}||()), values %$stash;
    50          
86 51         98 @{$INFO{$target}{not_methods}={}}{@not_methods} = @not_methods;
  51         186  
87             # a role does itself
88 51         125 $APPLIED_TO{$target} = { $target => undef };
89 51         295 foreach my $hook (@ON_ROLE_CREATE) {
90 2         7 $hook->($target);
91             }
92             }
93              
94             sub _install_subs {
95 1     1   3 my ($me, $target) = @_;
96 1 50       3 return if $me->is_role($target);
97             # install before/after/around subs
98 1         3 foreach my $type (qw(before after around)) {
99 3         9 *{_getglob "${target}::${type}"} = sub {
100 0   0 0   0 push @{$INFO{$target}{modifiers}||=[]}, [ $type => @_ ];
  0         0  
101 0         0 return;
102 3         12 };
103             }
104 1         4 *{_getglob "${target}::requires"} = sub {
105 0   0 0   0 push @{$INFO{$target}{requires}||=[]}, @_;
  0         0  
106 0         0 return;
107 1         14 };
108 1         3 *{_getglob "${target}::with"} = sub {
109 0     0   0 $me->apply_roles_to_package($target, @_);
110 0         0 return;
111 1         4 };
112             }
113              
114             sub role_application_steps {
115 36     36 0 154 qw(_install_methods _check_requires _install_modifiers _copy_applied_list);
116             }
117              
118             sub apply_single_role_to_package {
119 29     29 0 64 my ($me, $to, $role) = @_;
120              
121 29         78 _load_module($role);
122              
123 28 50       87 croak "This is apply_role_to_package" if ref($to);
124 28 50       86 croak "${role} is not a Role::Tiny" unless $me->is_role($role);
125              
126 28         103 foreach my $step ($me->role_application_steps) {
127 102         416 $me->$step($to, $role);
128             }
129             }
130              
131             sub _copy_applied_list {
132 39     39   85 my ($me, $to, $role) = @_;
133             # copy our role list into the target's
134 39   100     56 @{$APPLIED_TO{$to}||={}}{keys %{$APPLIED_TO{$role}}} = ();
  39         305  
  39         120  
135             }
136              
137             sub apply_roles_to_object {
138 38     38 0 4428 my ($me, $object, @roles) = @_;
139 38 50       137 croak "No roles supplied!" unless @roles;
140 38         72 my $class = ref($object);
141             # on perl < 5.8.9, magic isn't copied to all ref copies. bless the parameter
142             # directly, so at least the variable passed to us will get any magic applied
143 38         104 bless($_[1], $me->create_class_with_roles($class, @roles));
144             }
145              
146             my $role_suffix = 'A000';
147             sub _composite_name {
148 40     40   89 my ($me, $superclass, @roles) = @_;
149              
150 40         116 my $new_name = join(
151             '__WITH__', $superclass, my $compose_name = join '__AND__', @roles
152             );
153              
154 40 100       95 if (length($new_name) > 252) {
155 7   33     26 $new_name = $COMPOSED{abbrev}{$new_name} ||= do {
156 7         20 my $abbrev = substr $new_name, 0, 250 - length $role_suffix;
157 7         18 $abbrev =~ s/(?
158 7         49 $abbrev.'__'.$role_suffix++;
159             };
160             }
161 40 50       159 return wantarray ? ($new_name, $compose_name) : $new_name;
162             }
163              
164             sub create_class_with_roles {
165 41     41 0 90 my ($me, $superclass, @roles) = @_;
166              
167 41 50       94 croak "No roles supplied!" unless @roles;
168              
169 41         104 _load_module($superclass);
170             {
171 41         74 my %seen;
  41         55  
172 41 100       189 if (my @dupes = grep 1 == $seen{$_}++, @roles) {
173 1         7 croak "Duplicated roles: ".join(', ', @dupes);
174             }
175             }
176              
177 40         108 my ($new_name, $compose_name) = $me->_composite_name($superclass, @roles);
178              
179 40 50       110 return $new_name if $COMPOSED{class}{$new_name};
180              
181 40         72 foreach my $role (@roles) {
182 41         86 _load_module($role);
183 41 50       116 croak "${role} is not a Role::Tiny" unless $me->is_role($role);
184             }
185              
186 40         187 require(_MRO_MODULE);
187              
188 40         116 my $composite_info = $me->_composite_info_for(@roles);
189 40         72 my %conflicts = %{$composite_info->{conflicts}};
  40         119  
190 40 100       102 if (keys %conflicts) {
191             my $fail =
192             join "\n",
193             map {
194 1         4 "Method name conflict for '$_' between roles "
195 1         5 ."'".join("' and '", sort values %{$conflicts{$_}})."'"
  1         9  
196             .", cannot apply these simultaneously to an object."
197             } keys %conflicts;
198 1         12 croak $fail;
199             }
200              
201 39         109 my @composable = map $me->_composable_package_for($_), reverse @roles;
202              
203             # some methods may not exist in the role, but get generated by
204             # _composable_package_for (Moose accessors via Moo). filter out anything
205             # provided by the composable packages, excluding the subs we generated to
206             # make modifiers work.
207             my @requires = grep {
208 0         0 my $method = $_;
209 0   0     0 !grep $_->can($method) && !$COMPOSED{role}{$_}{modifiers_only}{$method},
210             @composable
211 39         58 } @{$composite_info->{requires}};
  39         75  
212              
213 39         113 $me->_check_requires(
214             $superclass, $compose_name, \@requires
215             );
216              
217 39         85 *{_getglob("${new_name}::ISA")} = [ @composable, $superclass ];
  39         105  
218              
219 39   50     247 @{$APPLIED_TO{$new_name}||={}}{
220 39         107 map keys %{$APPLIED_TO{$_}}, @roles
  39         118  
221             } = ();
222              
223 39         108 $COMPOSED{class}{$new_name} = 1;
224 39         291 return $new_name;
225             }
226              
227             # preserved for compat, and apply_roles_to_package calls it to allow an
228             # updated Role::Tiny to use a non-updated Moo::Role
229              
230 29     29 0 1373 sub apply_role_to_package { shift->apply_single_role_to_package(@_) }
231              
232             sub apply_roles_to_package {
233 34     34 0 94 my ($me, $to, @roles) = @_;
234              
235 34 100       182 return $me->apply_role_to_package($to, $roles[0]) if @roles == 1;
236              
237 10         21 my %conflicts = %{$me->_composite_info_for(@roles)->{conflicts}};
  10         49  
238 10         64 my @have = grep $to->can($_), keys %conflicts;
239 10         23 delete @conflicts{@have};
240              
241 10 100       31 if (keys %conflicts) {
242             my $fail =
243             join "\n",
244             map {
245 2         5 "Due to a method name conflict between roles "
246 2         4 ."'".join(' and ', sort values %{$conflicts{$_}})."'"
  2         41  
247             .", the method '$_' must be implemented by '${to}'"
248             } keys %conflicts;
249 2         194 croak $fail;
250             }
251              
252             # conflicting methods are supposed to be treated as required by the
253             # composed role. we don't have an actual composed role, but because
254             # we know the target class already provides them, we can instead
255             # pretend that the roles don't do for the duration of application.
256 8         32 my @role_methods = map $me->_concrete_methods_of($_), @roles;
257             # separate loops, since local ..., delete ... for ...; creates a scope
258 8         21 local @{$_}{@have} for @role_methods;
  16         71  
259 8         15 delete @{$_}{@have} for @role_methods;
  16         32  
260              
261             # the if guard here is essential since otherwise we accidentally create
262             # a $INFO for something that isn't a Role::Tiny (or Moo::Role) because
263             # autovivification hates us and wants us to die()
264 8 50       22 if ($INFO{$to}) {
265 0         0 delete $INFO{$to}{methods}; # reset since we're about to add methods
266             }
267              
268             # backcompat: allow subclasses to use apply_single_role_to_package
269             # to apply changes. set a local var so ours does nothing.
270 8         10 our %BACKCOMPAT_HACK;
271 8 100 33     166 if($me ne __PACKAGE__
    50 33        
272             and exists $BACKCOMPAT_HACK{$me} ? $BACKCOMPAT_HACK{$me} :
273             $BACKCOMPAT_HACK{$me} =
274             $me->can('role_application_steps')
275             == \&role_application_steps
276             && $me->can('apply_single_role_to_package')
277             != \&apply_single_role_to_package
278             ) {
279 0         0 foreach my $role (@roles) {
280 0         0 $me->apply_single_role_to_package($to, $role);
281             }
282             }
283             else {
284 8         23 foreach my $step ($me->role_application_steps) {
285 32         231 foreach my $role (@roles) {
286 64         410 $me->$step($to, $role);
287             }
288             }
289             }
290 8         77 $APPLIED_TO{$to}{join('|',@roles)} = 1;
291             }
292              
293             sub _composite_info_for {
294 50     50   127 my ($me, @roles) = @_;
295 50   66     260 $COMPOSITE_INFO{join('|', sort @roles)} ||= do {
296 19         46 foreach my $role (@roles) {
297 29         61 _load_module($role);
298             }
299 19         39 my %methods;
300 19         34 foreach my $role (@roles) {
301 29         100 my $this_methods = $me->_concrete_methods_of($role);
302 29         181 $methods{$_}{$this_methods->{$_}} = $role for keys %$this_methods;
303             }
304 19         37 my %requires;
305 19 100       36 @requires{map @{$INFO{$_}{requires}||[]}, @roles} = ();
  29         154  
306 19         77 delete $requires{$_} for keys %methods;
307 19         47 delete $methods{$_} for grep keys(%{$methods{$_}}) == 1, keys %methods;
  17         73  
308 19         167 +{ conflicts => \%methods, requires => [keys %requires] }
309             };
310             }
311              
312             sub _composable_package_for {
313 39     39   68 my ($me, $role) = @_;
314 39         112 my $composed_name = 'Role::Tiny::_COMPOSABLE::'.$role;
315 39 100       139 return $composed_name if $COMPOSED{role}{$composed_name};
316 9         31 $me->_install_methods($composed_name, $role);
317 9         33 my $base_name = $composed_name.'::_BASE';
318             # force stash to exist so ->can doesn't complain
319 9         27 _getstash($base_name);
320             # Not using _getglob, since setting @ISA via the typeglob breaks
321             # inheritance on 5.10.0 if the stash has previously been accessed an
322             # then a method called on the class (in that order!), which
323             # ->_install_methods (with the help of ->_install_does) ends up doing.
324 17     17   168 { no strict 'refs'; @{"${composed_name}::ISA"} = ( $base_name ); }
  17         60  
  17         8710  
  9         17  
  9         15  
  9         204  
325 9   50     71 my $modifiers = $INFO{$role}{modifiers}||[];
326 9         18 my @mod_base;
327             my @modifiers = grep !$composed_name->can($_),
328 9         15 do { my %h; @h{map @{$_}[1..$#$_-1], @$modifiers} = (); keys %h };
  9         12  
  9         19  
  0         0  
  9         21  
329 9         21 foreach my $modified (@modifiers) {
330 0         0 push @mod_base, "sub ${modified} { shift->next::method(\@_) }";
331             }
332 9         13 my $e;
333             {
334 9         13 local $@;
  9         15  
335 9         387 eval(my $code = join "\n", "package ${base_name};", @mod_base);
336 9 50       50 $e = "Evaling failed: $@\nTrying to eval:\n${code}" if $@;
337             }
338 9 50       20 die $e if $e;
339 9         39 $me->_install_modifiers($composed_name, $role);
340             $COMPOSED{role}{$composed_name} = {
341 9         36 modifiers_only => { map { $_ => 1 } @modifiers },
  0         0  
342             };
343 9         43 return $composed_name;
344             }
345              
346             sub _check_requires {
347 83     83   186 my ($me, $to, $name, $requires) = @_;
348 83 100 100     116 return unless my @requires = @{$requires||$INFO{$name}{requires}||[]};
  83 100       446  
349 21 100       227 if (my @requires_fail = grep !$to->can($_), @requires) {
350             # role -> role, add to requires, role -> class, error out
351 10 100       35 if (my $to_info = $INFO{$to}) {
352 5   100     9 push @{$to_info->{requires}||=[]}, @requires_fail;
  5         33  
353             } else {
354 5         414 croak "Can't apply ${name} to ${to} - missing ".join(', ', @requires_fail);
355             }
356             }
357             }
358              
359             sub _concrete_methods_of {
360 101     101   801 my ($me, $role) = @_;
361 101         170 my $info = $INFO{$role};
362             # grab role symbol table
363 101         189 my $stash = _getstash($role);
364             # reverse so our keys become the values (captured coderefs) in case
365             # they got copied or re-used since
366 101 100       168 my $not_methods = { reverse %{$info->{not_methods}||{}} };
  101         354  
367             $info->{methods} ||= +{
368             # grab all code entries that aren't in the not_methods list
369             map {;
370 17     17   135 no strict 'refs';
  17         50  
  17         5569  
371 110 100       166 my $code = exists &{"${role}::$_"} ? \&{"${role}::$_"} : undef;
  110         376  
  44         144  
372 110 100 100     613 ( ! $code or exists $not_methods->{$code} ) ? () : ($_ => $code)
373 101   33     537 } grep +(!ref($stash->{$_}) || ref($stash->{$_}) eq 'CODE'), keys %$stash
      100        
374             };
375             }
376              
377             sub methods_provided_by {
378 2     2 0 1301 my ($me, $role) = @_;
379 2 100       11 croak "${role} is not a Role::Tiny" unless $me->is_role($role);
380 1 50       2 (keys %{$me->_concrete_methods_of($role)}, @{$INFO{$role}->{requires}||[]});
  1         4  
  1         8  
381             }
382              
383             sub _install_methods {
384 53     53   108 my ($me, $to, $role) = @_;
385              
386 53         95 my $info = $INFO{$role};
387              
388 53         133 my $methods = $me->_concrete_methods_of($role);
389              
390             # grab target symbol table
391 53         124 my $stash = _getstash($to);
392              
393             # determine already extant methods of target
394 53         93 my %has_methods;
395             @has_methods{grep
396 53   50     209 +(ref($stash->{$_}) || *{$stash->{$_}}{CODE}),
397             keys %$stash
398             } = ();
399              
400 53         191 foreach my $i (grep !exists $has_methods{$_}, keys %$methods) {
401 17     17   140 no warnings 'once';
  17         56  
  17         10984  
402 41         133 my $glob = _getglob "${to}::${i}";
403 41         118 *$glob = $methods->{$i};
404              
405             # overloads using method names have the method stored in the scalar slot
406             # and &overload::nil in the code slot.
407             next
408             unless $i =~ /^\(/
409             && ((defined &overload::nil && $methods->{$i} == \&overload::nil)
410 41 50 0     182 || (defined &overload::_nil && $methods->{$i} == \&overload::_nil));
      33        
411              
412 0         0 my $overload = ${ *{_getglob "${role}::${i}"}{SCALAR} };
  0         0  
  0         0  
413             next
414 0 0       0 unless defined $overload;
415              
416 0         0 *$glob = \$overload;
417             }
418              
419 53         182 $me->_install_does($to);
420             }
421              
422             sub _install_modifiers {
423 48     48   111 my ($me, $to, $name) = @_;
424 48 100       195 return unless my $modifiers = $INFO{$name}{modifiers};
425 2         6 my $info = $INFO{$to};
426 2 50 100     13 my $existing = ($info ? $info->{modifiers} : $COMPOSED{modifiers}{$to}) ||= [];
427             my @modifiers = grep {
428 2         3 my $modifier = $_;
429 2         9 !grep $_ == $modifier, @$existing;
430 2 50       4 } @{$modifiers||[]};
  2         7  
431 2         5 push @$existing, @modifiers;
432              
433 2 50       5 if (!$info) {
434 2         5 foreach my $modifier (@modifiers) {
435 2         9 $me->_install_single_modifier($to, @$modifier);
436             }
437             }
438             }
439              
440             my $vcheck_error;
441              
442             sub _install_single_modifier {
443 2     2   6 my ($me, @args) = @_;
444 2 100       8 defined($vcheck_error) or $vcheck_error = do {
445 1         2 local $@;
446 1 50       12 eval {
447 1         9 require Class::Method::Modifiers;
448 1         15 Class::Method::Modifiers->VERSION(1.05);
449 1         6 1;
450             } ? 0 : $@;
451             };
452 2 50       5 $vcheck_error and die $vcheck_error;
453 2         7 Class::Method::Modifiers::install_modifier(@args);
454             }
455              
456             my $FALLBACK = sub { 0 };
457             sub _install_does {
458 53     53   117 my ($me, $to) = @_;
459              
460             # only add does() method to classes
461 53 100       117 return if $me->is_role($to);
462              
463 45         235 my $does = $me->can('does_role');
464             # add does() only if they don't have one
465 45 100       413 *{_getglob "${to}::does"} = $does unless $to->can('does');
  33         152  
466              
467             return
468 45 100 50     539 if $to->can('DOES') and $to->can('DOES') != (UNIVERSAL->can('DOES') || 0);
      66        
469              
470 32   33     177 my $existing = $to->can('DOES') || $to->can('isa') || $FALLBACK;
471             my $new_sub = sub {
472 4     4   9 my ($proto, $role) = @_;
473 4 50       9 $proto->$does($role) or $proto->$existing($role);
474 32         207 };
475 17     17   143 no warnings 'redefine';
  17         32  
  17         4314  
476 32         61 return *{_getglob "${to}::DOES"} = $new_sub;
  32         84  
477             }
478              
479             sub does_role {
480 34     34 0 6982 my ($proto, $role) = @_;
481 34         191 require(_MRO_MODULE);
482 34   66     56 foreach my $class (@{mro::get_linear_isa(ref($proto)||$proto)}) {
  34         209  
483 41 100       203 return 1 if exists $APPLIED_TO{$class}{$role};
484             }
485 9         43 return 0;
486             }
487              
488             sub is_role {
489 179     179 0 1787 my ($me, $role) = @_;
490 179   66     980 return !!($INFO{$role} && ($INFO{$role}{is_role} || $INFO{$role}{not_methods}));
491             }
492              
493             1;
494              
495             #pod =encoding utf8
496             #pod
497             #pod =head1 NAME
498             #pod
499             #pod Jojo::Role::Tiny - A fork of Role::Tiny for Jojo nefarious purposes
500             #pod
501             #pod =head1 DESCRIPTION
502             #pod
503             #pod Internal to L – don't use.
504             #pod
505             #pod =head1 SEE ALSO
506             #pod
507             #pod L
508             #pod
509             #pod =cut
510              
511             __END__