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 3 4 75.0
total 116 126 92.0


line stmt bran cond sub pod time code
1              
2             package Jojo::Role;
3             $Jojo::Role::VERSION = '0.5.0';
4             # ABSTRACT: Role::Tiny + lexical "with"
5 17     17   917883 use 5.018;
  17         187  
6 17     17   85 use strict;
  17         29  
  17         323  
7 17     17   69 use warnings;
  17         25  
  17         418  
8 17     17   8720 use utf8;
  17         205  
  17         74  
9 17     17   444 use feature ();
  17         31  
  17         216  
10 17     17   6074 use experimental ();
  17         47920  
  17         943  
11              
12             BEGIN {
13 17     17   8266 require Role::Tiny;
14 17         65049 Role::Tiny->VERSION('2.000006');
15 17         764 our @ISA = qw(Role::Tiny);
16             }
17              
18 17     17   6873 use Sub::Inject 0.3.0 ();
  17         6548  
  17         1190  
19              
20             # Aliasing of Role::Tiny symbols
21             BEGIN {
22 17     17   62 *INFO = \%Role::Tiny::INFO;
23 17         37 *APPLIED_TO = \%Role::Tiny::APPLIED_TO;
24 17         29 *COMPOSED = \%Role::Tiny::COMPOSED;
25 17         57 *COMPOSITE_INFO = \%Role::Tiny::COMPOSITE_INFO;
26 17         31 *ON_ROLE_CREATE = \@Role::Tiny::ON_ROLE_CREATE;
27              
28 17         12705 *_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              
41             # Jojo::Role->apply_roles_to_package('Some::Package', qw(Some::Role +Other::Role));
42             sub apply_roles_to_package {
43 34     34 1 1433 my ($self, $target) = (shift, shift);
44             return $self->Role::Tiny::apply_roles_to_package($target,
45 34 50       60 map { /^\+(.+)$/ ? "${target}::Role::$1" : $_ } @_);
  44         209  
46             }
47              
48             # Jojo::Role->create_class_with_roles('Some::Base', qw(Some::Role1 +Role2));
49             sub create_class_with_roles {
50 41     41 1 21569 my ($self, $target) = (shift, shift);
51             return $self->Role::Tiny::create_class_with_roles($target,
52 41 50       76 map { /^\+(.+)$/ ? "${target}::Role::$1" : $_ } @_);
  46         172  
53             }
54              
55             sub import {
56 74     74   13527 my $target = caller;
57 74         127 my $me = shift;
58              
59             # Jojo modules are strict!
60 74         1038 $_->import for qw(strict warnings utf8);
61 74         5414 feature->import(':5.18');
62 74         373 experimental->import('lexical_subs');
63              
64 74         2013 my $flag = shift;
65 74 100       205 if (!$flag) {
66 49         151 $me->make_role($target);
67 49         77 $flag = '-role';
68             }
69              
70 74   50     108 my @exports = @{$EXPORT_TAGS{$flag} // []};
  74         263  
71 74         181 @_ = $me->_generate_subs($target, @exports);
72 74         333 goto &Sub::Inject::sub_inject;
73             }
74              
75 74     74 0 171 sub role_provider { $_[0] }
76              
77             sub make_role {
78 50     50 1 164 my ($me, $target) = @_;
79 50 50       203 return if $me->is_role($target); # already exported into this package
80 50         406 $INFO{$target}{is_role} = 1;
81              
82             # get symbol table reference
83 50         122 my $stash = _getstash($target);
84              
85             # grab all *non-constant* (stash slot is not a scalarref) subs present
86             # in the symbol table and store their refaddrs (no need to forcibly
87             # inflate constant subs into real subs) with a map to the coderefs in
88             # case of copying or re-use
89             my @not_methods
90 50 50 66     595 = map +(ref $_ eq 'CODE' ? $_ : ref $_ ? () : *$_{CODE} || ()),
    50          
91             values %$stash;
92 50         87 @{$INFO{$target}{not_methods} = {}}{@not_methods} = @not_methods;
  50         137  
93              
94             # a role does itself
95 50         134 $APPLIED_TO{$target} = {$target => undef};
96 50         103 foreach my $hook (@ON_ROLE_CREATE) {
97 2         5 $hook->($target);
98             }
99 50         97 return;
100             }
101              
102             BEGIN {
103 17     17   103 %EXPORT_TAGS = ( #
104             -role => [qw(after around before requires with)],
105             -with => [qw(with)],
106             );
107              
108             %EXPORT_GEN = (
109             requires => sub {
110 49         104 my (undef, $target) = @_;
111             return sub {
112 11   50 8   5727 push @{$INFO{$target}{requires} ||= []}, @_;
  11         66  
113 11         158 return;
114 49         212 };
115             },
116             with => sub {
117 74         170 my ($me, $target) = (shift->role_provider, shift);
118             return sub {
119 33     27   11847 $me->apply_roles_to_package($target, @_);
120 27         6945 return;
121 74         499 };
122             },
123 17         107 );
124              
125             # before/after/around
126 17         57 foreach my $type (qw(before after around)) {
127             $EXPORT_GEN{$type} = sub {
128 147         238 my (undef, $target) = @_;
129             return sub {
130 2   50 2   2282 push @{$INFO{$target}{modifiers} ||= []}, [$type => @_];
  2         11  
131 2         5 return;
132 147         862 };
133 51         2243 };
134             }
135             }
136              
137             sub _generate_subs {
138 74     74   253 my ($class, $target) = (shift, shift);
139 74         141 return map { my $cb = $EXPORT_GEN{$_}; $_ => $class->$cb($target) } @_;
  270         504  
  270         486  
140             }
141              
142             1;
143              
144             #pod =encoding utf8
145             #pod
146             #pod =head1 SYNOPSIS
147             #pod
148             #pod package Some::Role {
149             #pod use Jojo::Role; # requires perl 5.18+
150             #pod
151             #pod sub foo {...}
152             #pod sub bar {...}
153             #pod around baz => sub {...};
154             #pod }
155             #pod
156             #pod package Some::Class {
157             #pod use Jojo::Role -with;
158             #pod with 'Some::Role';
159             #pod
160             #pod # bar gets imported, but not foo
161             #pod sub foo {...}
162             #pod
163             #pod # baz is wrapped in the around modifier by Class::Method::Modifiers
164             #pod sub baz {...}
165             #pod }
166             #pod
167             #pod =head1 DESCRIPTION
168             #pod
169             #pod L works kind of like L but C, C,
170             #pod C, C and C are exported
171             #pod as lexical subroutines.
172             #pod
173             #pod This is a companion to L.
174             #pod
175             #pod L may be used in two ways. First, to declare a role, which is done
176             #pod with
177             #pod
178             #pod use Jojo::Base;
179             #pod use Jojo::Base -role; # Longer version
180             #pod
181             #pod Second, to compose one or more roles into a class, via
182             #pod
183             #pod use Jojo::Base -with;
184             #pod
185             #pod =head1 IMPORTED SUBROUTINES: TAG C<-role>
186             #pod
187             #pod The C<-role> tag exports the following subroutines into the caller.
188             #pod
189             #pod =head2 after
190             #pod
191             #pod after foo => sub { ... };
192             #pod
193             #pod Declares an
194             #pod L<< "after" | Class::Method::Modifiers/after method(s) => sub { ... } >>
195             #pod modifier to be applied to the named method at composition time.
196             #pod
197             #pod =head2 around
198             #pod
199             #pod around => sub { ... };
200             #pod
201             #pod Declares an
202             #pod L<< "around" | Class::Method::Modifiers/around method(s) => sub { ... } >>
203             #pod modifier to be applied to the named method at composition time.
204             #pod
205             #pod =head2 before
206             #pod
207             #pod before => sub { ... };
208             #pod
209             #pod Declares a
210             #pod L<< "before" | Class::Method::Modifiers/before method(s) => sub { ... } >>
211             #pod modifier to be applied to the named method at composition time.
212             #pod
213             #pod =head2 requires
214             #pod
215             #pod requires qw(foo bar);
216             #pod
217             #pod Declares a list of methods that must be defined to compose the role.
218             #pod
219             #pod =head2 with
220             #pod
221             #pod with 'Some::Role';
222             #pod
223             #pod with 'Some::Role1', 'Some::Role2';
224             #pod
225             #pod Composes one or more roles into the current role.
226             #pod
227             #pod =head1 IMPORTED SUBROUTINES: TAG C<-with>
228             #pod
229             #pod The C<-with> tag exports the following subroutine into the caller.
230             #pod It is equivalent to using L.
231             #pod
232             #pod =head2 with
233             #pod
234             #pod with 'Some::Role1', 'Some::Role2';
235             #pod
236             #pod Composes one or more roles into the current class.
237             #pod
238             #pod =head1 METHODS
239             #pod
240             #pod L inherits all methods from L and implements the
241             #pod following new ones.
242             #pod
243             #pod =head2 apply_roles_to_package
244             #pod
245             #pod Jojo::Role->apply_roles_to_package('Some::Package', qw(Some::Role +Other::Role));
246             #pod
247             #pod =head2 create_class_with_roles
248             #pod
249             #pod Jojo::Role->create_class_with_roles('Some::Base', qw(Some::Role1 +Role2));
250             #pod
251             #pod =head2 import
252             #pod
253             #pod Jojo::Role->import();
254             #pod Jojo::Role->import(-role);
255             #pod Jojo::Role->import(-with);
256             #pod
257             #pod =head2 make_role
258             #pod
259             #pod Role::Tiny->make_role('Some::Package');
260             #pod
261             #pod Promotes a given package to a role.
262             #pod No subroutines are imported into C<'Some::Package'>.
263             #pod
264             #pod =head1 CAVEATS
265             #pod
266             #pod =over 4
267             #pod
268             #pod =item *
269             #pod
270             #pod L requires perl 5.18 or newer
271             #pod
272             #pod =item *
273             #pod
274             #pod Because a lexical sub does not behave like a package import,
275             #pod some code may need to be enclosed in blocks to avoid warnings like
276             #pod
277             #pod "state" subroutine &with masks earlier declaration in same scope at...
278             #pod
279             #pod =back
280             #pod
281             #pod =head1 SEE ALSO
282             #pod
283             #pod L, L.
284             #pod
285             #pod =head1 ACKNOWLEDGMENTS
286             #pod
287             #pod Thanks to the authors of L, which hold
288             #pod the copyright over the original code.
289             #pod
290             #pod =cut
291              
292             __END__