File Coverage

blib/lib/Jojo/Role.pm
Criterion Covered Total %
statement 82 82 100.0
branch 7 12 58.3
condition 5 9 55.5
subroutine 19 19 100.0
pod 2 3 66.6
total 115 125 92.0


line stmt bran cond sub pod time code
1              
2             package Jojo::Role;
3             $Jojo::Role::VERSION = '0.4.0';
4             # ABSTRACT: Role::Tiny + lexical "with"
5 16     16   705069 use 5.018;
  16         177  
6 16     16   76 use strict;
  16         25  
  16         274  
7 16     16   59 use warnings;
  16         22  
  16         337  
8 16     16   5436 use utf8;
  16         190  
  16         69  
9 16     16   410 use feature ();
  16         24  
  16         177  
10 16     16   3566 use experimental ();
  16         36623  
  16         817  
11              
12             BEGIN {
13 16     16   5200 require Role::Tiny;
14 16         50087 Role::Tiny->VERSION('2.000005');
15 16         605 our @ISA = qw(Role::Tiny);
16             }
17              
18 16     16   4276 use Sub::Inject 0.3.0 ();
  16         5562  
  16         1077  
19              
20             # Aliasing of Role::Tiny symbols
21             BEGIN {
22 16     16   62 *INFO = \%Role::Tiny::INFO;
23 16         35 *APPLIED_TO = \%Role::Tiny::APPLIED_TO;
24 16         29 *COMPOSED = \%Role::Tiny::COMPOSED;
25 16         28 *COMPOSITE_INFO = \%Role::Tiny::COMPOSITE_INFO;
26 16         27 *ON_ROLE_CREATE = \@Role::Tiny::ON_ROLE_CREATE;
27              
28 16         9687 *_getstash = \&Role::Tiny::_getstash;
29             }
30              
31             our %INFO;
32             our %APPLIED_TO;
33             our %COMPOSED;
34             our %COMPOSITE_INFO;
35             our @ON_ROLE_CREATE;
36              
37             our %EXPORT_TAGS;
38             our %EXPORT_GEN;
39              
40             # Jojo::Role->apply_roles_to_package('Some::Package', qw(Some::Role +Other::Role));
41             sub apply_roles_to_package {
42 33     33 1 68 my ($self, $target) = (shift, shift);
43             return $self->Role::Tiny::apply_roles_to_package($target,
44 33 50       64 map { /^\+(.+)$/ ? "${target}::Role::$1" : $_ } @_);
  43         214  
45             }
46              
47             # Jojo::Role->create_class_with_roles('Some::Base', qw(Some::Role1 +Role2));
48             sub create_class_with_roles {
49 41     41 1 18670 my ($self, $target) = (shift, shift);
50             return $self->Role::Tiny::create_class_with_roles($target,
51 41 50       75 map { /^\+(.+)$/ ? "${target}::Role::$1" : $_ } @_);
  46         181  
52             }
53              
54             sub import {
55 74     74   11136 my $target = caller;
56 74         111 my $me = shift;
57              
58             # Jojo modules are strict!
59 74         974 $_->import for qw(strict warnings utf8);
60 74         4438 feature->import(':5.18');
61 74         310 experimental->import('lexical_subs');
62              
63 74         1757 my $flag = shift;
64 74 100       167 if (!$flag) {
65 49         117 $me->_become_role($target);
66 49         59 $flag = '-role';
67             }
68              
69 74   50     86 my @exports = @{$EXPORT_TAGS{$flag} // []};
  74         226  
70 74         152 @_ = $me->_generate_subs($target, @exports);
71 74         310 goto &Sub::Inject::sub_inject;
72             }
73              
74 33     33 0 130 sub role_provider { $_[0] }
75              
76             sub _become_role {
77 49     49   84 my ($me, $target) = @_;
78 49 50       168 return if $me->is_role($target); # already exported into this package
79 49         331 $INFO{$target}{is_role} = 1;
80              
81             # get symbol table reference
82 49         101 my $stash = _getstash($target);
83              
84             # grab all *non-constant* (stash slot is not a scalarref) subs present
85             # in the symbol table and store their refaddrs (no need to forcibly
86             # inflate constant subs into real subs) with a map to the coderefs in
87             # case of copying or re-use
88             my @not_methods
89 49 50 66     510 = map +(ref $_ eq 'CODE' ? $_ : ref $_ ? () : *$_{CODE} || ()),
    50          
90             values %$stash;
91 49         101 @{$INFO{$target}{not_methods} = {}}{@not_methods} = @not_methods;
  49         121  
92              
93             # a role does itself
94 49         105 $APPLIED_TO{$target} = {$target => undef};
95 49         78 foreach my $hook (@ON_ROLE_CREATE) {
96 2         5 $hook->($target);
97             }
98 49         87 return;
99             }
100              
101             BEGIN {
102 16     16   98 %EXPORT_TAGS = ( #
103             -role => [qw(after around before requires with)],
104             -with => [qw(with)],
105             );
106              
107             %EXPORT_GEN = (
108             requires => sub {
109 49         81 my (undef, $target) = @_;
110             return sub {
111 11   50 8   4618 push @{$INFO{$target}{requires} ||= []}, @_;
  11         81  
112 11         208 return;
113 49         193 };
114             },
115             with => sub {
116 74         140 my ($me, $target) = @_;
117             return sub {
118 33     27   9706 $me->role_provider->apply_roles_to_package($target, @_);
119 27         7598 return;
120 74         388 };
121             },
122 16         87 );
123              
124             # before/after/around
125 16         41 foreach my $type (qw(before after around)) {
126             $EXPORT_GEN{$type} = sub {
127 147         192 my (undef, $target) = @_;
128             return sub {
129 2   50 2   2299 push @{$INFO{$target}{modifiers} ||= []}, [$type => @_];
  2         14  
130 2         5 return;
131 147         485 };
132 48         1362 };
133             }
134             }
135              
136             sub _generate_subs {
137 74     74   262 my ($class, $target) = (shift, shift);
138 74         114 return map { my $cb = $EXPORT_GEN{$_}; $_ => $class->$cb($target) } @_;
  270         387  
  270         390  
139             }
140              
141             1;
142              
143             #pod =encoding utf8
144             #pod
145             #pod =head1 SYNOPSIS
146             #pod
147             #pod package Some::Role {
148             #pod use Jojo::Role; # requires perl 5.18+
149             #pod
150             #pod sub foo {...}
151             #pod sub bar {...}
152             #pod around baz => sub {...};
153             #pod }
154             #pod
155             #pod package Some::Class {
156             #pod use Jojo::Role -with;
157             #pod with 'Some::Role';
158             #pod
159             #pod # bar gets imported, but not foo
160             #pod sub foo {...}
161             #pod
162             #pod # baz is wrapped in the around modifier by Class::Method::Modifiers
163             #pod sub baz {...}
164             #pod }
165             #pod
166             #pod =head1 DESCRIPTION
167             #pod
168             #pod L works kind of like L but C, C,
169             #pod C, C and C are imported
170             #pod as lexical subroutines.
171             #pod
172             #pod This is a companion to L.
173             #pod
174             #pod =head1 CAVEATS
175             #pod
176             #pod =over 4
177             #pod
178             #pod =item *
179             #pod
180             #pod L requires perl 5.18 or newer
181             #pod
182             #pod =item *
183             #pod
184             #pod Because a lexical sub does not behave like a package import,
185             #pod some code may need to be enclosed in blocks to avoid warnings like
186             #pod
187             #pod "state" subroutine &has masks earlier declaration in same scope at...
188             #pod
189             #pod =back
190             #pod
191             #pod =head1 SEE ALSO
192             #pod
193             #pod L, L.
194             #pod
195             #pod =cut
196              
197             __END__