File Coverage

blib/lib/MooseX/Role/Pluggable.pm
Criterion Covered Total %
statement 66 67 98.5
branch 16 18 88.8
condition n/a
subroutine 10 10 100.0
pod 1 1 100.0
total 93 96 96.8


line stmt bran cond sub pod time code
1             package MooseX::Role::Pluggable;
2             $MooseX::Role::Pluggable::VERSION = '0.07';
3             # ABSTRACT: add plugins to your Moose classes
4 3     3   13188 use Class::Load 'load_class';
  3         8  
  3         177  
5 3     3   469 use Moose::Role;
  3         5124  
  3         23  
6 3     3   16312 use Moose::Util::TypeConstraints;
  3         11  
  3         22  
7 3     3   8146 use Tie::IxHash;
  3         13274  
  3         86  
8 3     3   55 use 5.010;
  3         11  
9              
10             has plugins => (
11             isa => 'ArrayRef[Str|HashRef]' ,
12             is => 'rw' ,
13             );
14              
15             subtype 'MooseXRolePluggablePlugin'
16             => as 'Object'
17             => where { $_->does( 'MooseX::Role::Pluggable::Plugin' ) }
18             => message { 'Plugin did not consume the required role!' };
19              
20             has plugin_hash => (
21             isa => 'Maybe[HashRef[MooseXRolePluggablePlugin]]' ,
22             is => 'ro' ,
23             init_arg => undef ,
24             lazy_build => 1 ,
25             );
26              
27             sub _build_plugin_hash {
28 3     3   9 my $self = shift;
29              
30             return $self->plugin_list
31 3 100       84 ? { map { $_->_mxrp_name => $_ } @{ $self->plugin_list } }
  6         229  
  2         52  
32             : undef;
33             }
34              
35             has plugin_list => (
36             isa => 'Maybe[ArrayRef[MooseXRolePluggablePlugin]]' ,
37             is => 'ro' ,
38             init_arg => undef ,
39             lazy_build => 1 ,
40             );
41              
42             sub _build_plugin_list {
43 3     3   9 my( $self ) = shift;
44              
45 3 100       100 return undef unless $self->plugins;
46              
47 2         6 my $plugin_list = [];
48              
49 2         10 my $plugin_name_map = $self->_map_plugins_to_libs();
50              
51 2         5 my @plugin_list = @{ $self->plugins };
  2         69  
52              
53 2         7 foreach my $plugin ( @plugin_list ) {
54 6         15 my( $plugin_name , $plugin_args );
55              
56 6 100       28 if ( ref $plugin eq 'HASH' ) { ( $plugin_name , $plugin_args ) = %$plugin }
  2         8  
57 4         7 else { $plugin_name = $plugin }
58              
59 6         17 $plugin_name =~ s/^\+//;
60              
61 6         33 my $plugin_lib = $plugin_name_map->{$plugin_name};
62              
63             ### FIXME should have some Try::Tiny here, with a parameter to control
64             ### what happens when a class doesn't load -- ignore, warn, die
65 6         77 load_class( $plugin_lib );
66              
67 6         248 my $args = {};
68 6 100       16 if ( $plugin_args ) {
69 2         5 $args = $plugin_args;
70 2         5 foreach ( qw/ _mxrp_name _mxrp_parent / ) {
71             die "'$_' is used internally and cannot be passed to constructor"
72 4 50       22 if exists $plugin_args->{$_};
73             }
74 2         5 $args->{_mxrp_name} = $plugin_name;
75 2         5 $args->{_mxrp_parent} = $self;
76             }
77             else {
78 4         15 $args = {
79             _mxrp_name => $plugin_name ,
80             _mxrp_parent => $self ,
81             };
82             }
83              
84 6         45 my $loaded_plugin = $plugin_lib->new($args);
85              
86 6         3075 push @{ $plugin_list } , $loaded_plugin;
  6         31  
87             }
88              
89 2         73 return $plugin_list;
90             };
91              
92             sub plugin_run_method {
93 2     2 1 1194 my( $self , $method ) = @_;
94              
95 2         7 my $return = [];
96 2         7 foreach my $plugin ( @{ $self->plugin_list }) {
  2         70  
97 6 100       38 if ( $plugin->can( $method ) ) {
98 4         18 push @$return , $plugin->$method();
99             }
100             }
101 2         17 return $return;
102             }
103              
104             sub _map_plugins_to_libs {
105 2     2   7 my( $self ) = @_;
106 2         5 my $class = ref $self;
107              
108 2         14 tie my %map, "Tie::IxHash";
109 2         37 my @plugins = @{ $self->plugins };
  2         53  
110              
111 2         8 foreach ( @plugins ) {
112 6         75 my $name;
113 6 100       21 if ( ref $_ eq 'HASH' ) { ($name) = keys %$_ }
  2 50       7  
114 0         0 elsif ( ref $_ ) { die "bad plugin list" }
115 4         7 else { $name = $_ }
116              
117 6 100       46 $map{$name} = ( $name =~ s/^\+// ) ? $name : "${class}::Plugin::$name";
118             }
119              
120 2         32 return \%map;
121             }
122              
123 3     3   43 no Moose::Role;
  3         7  
  3         21  
124             1;
125              
126             __END__
127              
128             =pod
129              
130             =encoding UTF-8
131              
132             =head1 NAME
133              
134             MooseX::Role::Pluggable - add plugins to your Moose classes
135              
136             =head1 VERSION
137              
138             version 0.07
139              
140             =head1 SYNOPSIS
141              
142             package MyMoose;
143             use Moose;
144             with 'MooseX::Role::Pluggable';
145              
146             my $moose = MyMoose->new({
147             plugins => [ 'Antlers' , 'Tail' , '+After::Market::GroundEffectsPackage' ] ,
148             # other args here
149             });
150              
151             or, if you need to pass arguments to a plugin module, you can do that by
152             using a hashref in the plugin list:
153              
154             my $moose = MyMoose->new({
155             plugins => [
156             { 'Antlers' => { long => 1, } },
157             { 'Tail' => { short => 1, } },
158             '+After::Market::GroundEffectsPackage' => {},
159             ]
160             });
161              
162             foreach my $plugin ( @{ $moose->plugin_list } ) {
163             if ( $plugin->can( 'some_method' )) {
164             $plugin->some_method();
165             }
166             }
167              
168             # call a method in a particular plugin directly
169             # (plugin_hash() returns a hash ref of 'plugin_name => plugin')
170             $moose->plugin_hash->{Antlers}->gore( $other_moose );
171              
172             # plugins are indexed by the name that was used in the original 'plugins' list
173             $moose->plugin_hash->{After::Market::GroundEffectsPackage}->light_up();
174              
175             # see the documentation for MooseX::Role::Pluggable::Plugin for info on
176             # how to write plugins...
177              
178             =head1 DESCRIPTION
179              
180             This is a role that allows your class to consume an arbitrary set of plugin
181             modules and then access those plugins and use them to do stuff.
182              
183             Plugins are loaded based on the list of plugin names in the 'plugins'
184             attribute. Names that start with a '+' are used as the full name to load;
185             names that don't start with a leading '+' are assumed to be in a 'Plugins'
186             namespace under your class name. (E.g., if your app is 'MyApp', plugins will
187             be loaded from 'MyApp::Plugin').
188              
189             NOTE: Plugins are lazily loaded -- that is, no plugins will be loaded until
190             either the 'plugin_list' or 'plugin_hash' methods are called. If you want to
191             force plugins to load at object instantiation time, your best bet is to call
192             one of those method right after you call 'new()'.
193              
194             Plugin classes should consume the 'MooseX::Role::Pluggable::Plugin' role; see
195             the documentation for that module for more information.
196              
197             =head1 NAME
198              
199             MooseX::Role::Pluggable - add plugins to your Moose classes
200              
201             =head1 METHODS
202              
203             =head2 plugin_hash
204              
205             Returns a hashref with a mapping of 'plugin_name' to the actual plugin object.
206              
207             =head2 plugin_list
208              
209             Returns an arrayref of loaded plugin objects. The arrayref will have the
210             same order as the plugins array ref passed in during object creation.
211              
212             =head2 plugin_run_method( $method_name )
213              
214             Looks for a method named $method_name in each plugin in the plugin list. If a
215             method of given name is found, it will be run. (N.b., this is essentially the
216             C<foreach> loop from the L</SYNOPSIS>.)
217              
218             =head1 AUTHOR
219              
220             John SJ Anderson, C<genehack@genehack.org>
221              
222             =head1 CONTRIBUTORS
223              
224             Sean Maguire C<smaguire@talktalkplc.com>
225              
226             =head1 SEE ALSO
227              
228             L<MooseX::Role::Pluggable::Plugin>, L<MooseX::Object::Pluggable>,
229             L<Object::Pluggable>
230              
231             =head1 COPYRIGHT AND LICENSE
232              
233             Copyright 2014, John SJ Anderson
234              
235             This program is free software; you can redistribute it and/or modify it
236             under the same terms as Perl itself.
237              
238             =head1 AUTHOR
239              
240             John SJ Anderson <john@genehack.org>
241              
242             =head1 COPYRIGHT AND LICENSE
243              
244             This software is copyright (c) 2020 by John SJ Anderson.
245              
246             This is free software; you can redistribute it and/or modify it under
247             the same terms as the Perl 5 programming language system itself.
248              
249             =cut