File Coverage

blib/lib/MooX/CaptainHook.pm
Criterion Covered Total %
statement 107 107 100.0
branch 26 30 86.6
condition 12 20 60.0
subroutine 28 28 100.0
pod 3 3 100.0
total 176 188 93.6


line stmt bran cond sub pod time code
1             package MooX::CaptainHook;
2              
3 10     10   877855 use 5.008;
  10         34  
  10         385  
4 10     10   58 use strict;
  10         17  
  10         302  
5 10     10   92 use warnings;
  10         21  
  10         436  
6              
7 10         157 use Sub::Exporter::Progressive -setup => {
8             exports => [qw/ on_application on_inflation is_role /],
9 10     10   54 };
  10         15  
10              
11             BEGIN {
12 10     10   1500 no warnings 'once';
  10         22  
  10         479  
13 10     10   27 $MooX::CaptainHook::AUTHORITY = 'cpan:TOBYINK';
14 10         2663 $MooX::CaptainHook::VERSION = '0.010';
15             }
16              
17             our %on_application;
18             our %on_inflation;
19              
20             sub is_role
21             {
22 37     37 1 75 my $package = shift;
23 37         199 require Role::Tiny;
24 37 100       161 return !!1 if exists $Role::Tiny::INFO{$package};
25 29 100       197 return !!0 if exists $Moo::MAKERS{$package};
26 2 50 33     36 if ($INC{'Class/MOP.pm'} and my $classof = 'Class::MOP'->can('class_of')) {
27 2         7 my $meta = $classof->($package);
28 2 100 66     58 return !!1 if $meta && $meta->isa('Moose::Meta::Role');
29             }
30 1         31 return !!0;
31             }
32              
33             {
34             my %already;
35             sub _fire
36             {
37 31     31   89 my (undef, $callbacks, $key, $args) = @_;
38 31 50 66     212 return if defined $key && $already{$key}++;
39 31 100       157 return unless $callbacks;
40 30         66 for my $cb (@$callbacks)
41             {
42 48         33625 $cb->($args) for $args->[0]; # local $_ aliasing
43             }
44             }
45             }
46              
47 10         20 use constant ON_APPLICATION => do {
48             package MooX::CaptainHook::OnApplication;
49             BEGIN {
50 10     10   61 no warnings 'once';
  10         26  
  10         474  
51 10     10   18 $MooX::CaptainHook::OnApplication::AUTHORITY = 'cpan:TOBYINK';
52 10         196 $MooX::CaptainHook::OnApplication::VERSION = '0.010';
53             }
54 10     10   2031 use Moo::Role;
  10         16733  
  10         82  
55             after apply_roles_to_package => sub
56             {
57 8         62482 my ($toolage, $package, @roles) = @_;
58            
59 8         24 for my $role (@roles)
60             {
61 9         95 'MooX::CaptainHook'->_fire(
62             $on_application{$role},
63             "OnApplication: $package $role",
64             [ $package, $role ],
65             );
66            
67             # This stuff is for internals...
68 9 50 50     61 push @{ $on_application{$package} ||= [] }, @{ $on_application{$role} || [] }
  1 100       9  
  1         5  
69             if MooX::CaptainHook::is_role($package);
70 9 100 100     19 push @{ $on_inflation{$package} ||= [] }, @{ $on_inflation{$role} || [] };
  9         51  
  9         270  
71             }
72 10         94 };
73 10         30572 __PACKAGE__;
74 10     10   7992 };
  10         19  
75              
76             # This sub makes sure that when a role which has an on_application hook
77             # gets inflated to a full Moose role (as will happen if the role is
78             # consumed by a Moose class!) then the generated metarole object will
79             # have a trait that still triggers the on_application hook.
80             #
81             # There are probably numerous edge cases not catered for, but my simple
82             # tests seem to work.
83             #
84             sub _inflated
85             {
86 20     20   45 my $args = shift;
87 20         47 my $meta = $args->[0];
88 20 100       221 return unless $meta->isa('Moose::Meta::Role');
89 6         58 require Moose::Util::MetaRole;
90 6     4   646 $args->[0] = $meta = Moose::Util::MetaRole::apply_metaroles(
  4     4   21  
  4     4   8  
  4     2   226  
  4     2   11  
  4     2   82  
  4         1866  
  4         10696  
  4         34  
  2         11  
  2         5  
  2         119  
  2         5  
  2         30  
  2         10  
  2         5  
  2         14  
91             for => $meta->name,
92             role_metaroles => {
93             role => eval q{
94             package MooX::CaptainHook::OnApplication::Moose;
95             BEGIN {
96             no warnings 'once';
97             $MooX::CaptainHook::OnApplication::Moose::AUTHORITY = 'cpan:TOBYINK';
98             $MooX::CaptainHook::OnApplication::Moose::VERSION = '0.010';
99             }
100             use Moose::Role;
101             after apply => sub {
102             my $role = $_[0]->name;
103             my $package = $_[1]->name;
104            
105             'MooX::CaptainHook'->_fire(
106             $on_application{$role},
107             "OnApplication: $package $role",
108             [ $package, $role ],
109             );
110            
111             # This stuff is for internals...
112             if (MooX::CaptainHook::is_role($_[1]->name)) {
113             push @{ $on_application{$package} ||= [] }, @{ $on_application{$role} || [] };
114             Moose::Util::MetaRole::apply_metaroles(
115             for => $package,
116             role_metaroles => {
117             role => [__PACKAGE__],
118             },
119             );
120             }
121             };
122             [__PACKAGE__];
123             },
124             },
125             );
126             }
127              
128             sub on_application (&;$)
129             {
130 8     8 1 80 my ($code, $role) = @_;
131 8 100       40 $role = caller unless defined $role;
132 8   50     14 push @{$on_application{$role}||=[]}, $code;
  8         81  
133            
134 8 100       56 'Moo::Role'->apply_single_role_to_package('Moo::Role', ON_APPLICATION)
135             unless Role::Tiny::does_role('Moo::Role', ON_APPLICATION);
136            
137 8         14454 return;
138             }
139              
140 10         19 use constant ON_INFLATION => do {
141             package MooX::CaptainHook::OnInflation;
142             BEGIN {
143 10     10   74 no warnings 'once';
  10         19  
  10         405  
144 10     10   22 $MooX::CaptainHook::OnInflation::AUTHORITY = 'cpan:TOBYINK';
145 10         170 $MooX::CaptainHook::OnInflation::VERSION = '0.010';
146             }
147 10     10   49 use Moo::Role;
  10         16  
  10         42  
148             around inject_real_metaclass_for => sub
149             {
150 20         588390 my ($orig, $pkg) = @_;
151 20         108 my $args = [ scalar $orig->($pkg) ];
152 20 100       199 'MooX::CaptainHook'->_fire(
153             [
154             'MooX::CaptainHook'->can('_inflated'),
155 20         107225 @{$on_inflation{$pkg}||[]}
156             ],
157             undef,
158             $args,
159             );
160 20         13206 return $args->[0];
161 10         62 };
162 10         1811 __PACKAGE__;
163 10     10   6603 };
  10         19  
164              
165             sub on_inflation (&;$)
166             {
167 16     16 1 82 my ($code, $pkg) = @_;
168 16 100       65 $pkg = caller unless defined $pkg;
169 16   100     22 push @{$on_inflation{$pkg}||=[]}, $_[0];
  16         112  
170              
171 16         7785 return;
172             }
173              
174             {
175             package MooX::CaptainHook::HandleMoose::Hack;
176             our $AUTHORITY = 'cpan:TOBYINK';
177             our $VERSION = '0.010';
178 10     10   15584 use overload qw[bool] => sub { 0 };
  10     13   10225  
  10         122  
  13         66209  
179             sub DESTROY {
180 5 50 33 5   1407910 'Moo::Role'->apply_single_role_to_package('Moo::HandleMoose', MooX::CaptainHook::ON_INFLATION)
181             if Moo::HandleMoose->can('inject_real_metaclass_for')
182             && !Role::Tiny::does_role('Moo::HandleMoose', MooX::CaptainHook::ON_INFLATION);
183             }
184             if ($Moo::HandleMoose::SETUP_DONE)
185             { __PACKAGE__->DESTROY }
186             else
187             { $Moo::HandleMoose::SETUP_DONE ||= bless [] }
188             }
189              
190             1;
191              
192             __END__
193              
194             =pod
195              
196             =encoding utf8
197              
198             =for stopwords MooX metaclass
199              
200             =head1 NAME
201              
202             MooX::CaptainHook - hooks for MooX modules
203              
204             =head1 SYNOPSIS
205              
206             {
207             package Local::Role;
208             use Moo::Role;
209             use MooX::CaptainHook qw(on_application);
210            
211             on_application {
212             print "Local::Role applied to $_\n";
213             };
214             }
215            
216             {
217             package Local::Class;
218             use Moo;
219             with 'Local::Role'; # "Local::Role applied to Local::Class"
220             }
221              
222             =head1 DESCRIPTION
223              
224             Although developed to support L<MooX::ClassAttribute>,
225             C<MooX::CaptainHook> provides a feature which may be of use to other
226             people writing Moo roles and MooX modules.
227              
228             This module allows you to run callback code when various events happen
229             to Moo classes and roles. Callback code for a role will also be copied
230             as hooks for any packages that consume that role.
231              
232             =over
233              
234             =item C<< on_application { BLOCK } >>
235              
236             The C<on_application> hook allows you to run a callback when your role
237             is applied to a class or other role. Within the callback C<< $_[0][0] >>
238             is set to the name of the package that the role is being applied to.
239              
240             Also C<< $_[0][1] >> is set to the name of the role being applied, which
241             may not be the same as the role where the hook was initially defined. (For
242             example, when role X establishes a hook; role X is consumed by role Y; and
243             role Y is consumed by class Z. Then the callback code will run twice, once
244             with C<< $_[0] = [qw(Y X)] >> and once with C<< $_[0] = [qw(Z Y)] >>.)
245              
246             Altering the C<< $_[0] >> arrayref will alter what is passed to subsequent
247             callbacks, so is not recommended.
248              
249             =item C<< on_inflation { BLOCK } >>
250              
251             The C<on_inflation> hook runs if your class or role is "inflated" to a
252             full Moose class or role. C<< $_[0][0] >> is the associated metaclass.
253              
254             Setting C<< $_[0][0] >> to a new meta object should "work" (whatever that
255             means).
256              
257             =item C<< is_role($package) >>
258              
259             Returns a boolean indicating whether the package is a role.
260              
261             =back
262              
263             Within callback code blocks, C<< $_ >> is also available as a convenient
264             alias to C<< $_[0][0] >>.
265              
266             =head2 Installing Hooks for Other Packages
267              
268             You can pass a package name as an optional second parameter:
269              
270             use MooX::CaptainHook;
271            
272             MooX::CaptainHook::on_application {
273             my ($applied_to, $role) = @_;
274             ...;
275             } 'Your::Role';
276              
277             =begin private
278              
279             =item ON_APPLICATION
280              
281             =item ON_INFLATION
282              
283             =end private
284              
285             =head1 BUGS
286              
287             Please report any bugs to
288             L<http://rt.cpan.org/Dist/Display.html?Queue=MooX-ClassAttribute>.
289              
290             =head1 SEE ALSO
291              
292             L<Moo>, L<MooX::ClassAttribute>.
293              
294             =head1 AUTHOR
295              
296             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
297              
298             =head1 COPYRIGHT AND LICENCE
299              
300             This software is copyright (c) 2013 by Toby Inkster.
301              
302             This is free software; you can redistribute it and/or modify it under
303             the same terms as the Perl 5 programming language system itself.
304              
305             =head1 DISCLAIMER OF WARRANTIES
306              
307             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
308             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
309             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
310