File Coverage

blib/lib/Module/PluginFinder.pm
Criterion Covered Total %
statement 77 79 97.4
branch 28 30 93.3
condition n/a
subroutine 11 11 100.0
pod 5 5 100.0
total 121 125 96.8


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2007-2009 -- leonerd@leonerd.org.uk
5              
6             package Module::PluginFinder;
7              
8 6     6   322824 use strict;
  6         19  
  6         252  
9 6     6   36 use warnings;
  6         11  
  6         185  
10              
11 6     6   42 use Carp;
  6         13  
  6         659  
12              
13 6     6   7120 use Module::Pluggable::Object;
  6         94026  
  6         3574  
14              
15             our $VERSION = '0.04';
16              
17             =head1 NAME
18              
19             C<Module::PluginFinder> - automatically choose the most appropriate plugin
20             module.
21              
22             =head1 SYNOPSIS
23              
24             use Module::PluginFinder;
25              
26             my $finder = Module::PluginFinder->new(
27             search_path => 'MyApp::Plugin',
28              
29             filter => sub {
30             my ( $module, $searchkey ) = @_;
31             $module->can( $searchkey );
32             },
33             );
34              
35             my $ball = $finder->construct( "bounce" );
36             $ball->bounce();
37              
38             my $fish = $finder->construct( "swim" );
39             $fish->swim();
40              
41             =head1 DESCRIPTION
42              
43             This module provides a factory class. Objects in this class search for a
44             specific plugin module to fit some criteria. Each time a new object is to be
45             constructed by the factory, the caller should provide a value which in some
46             way indicates the kind of object required. The factory's filter function is
47             then used to determine which plugin module fits the criteria.
48              
49             The most flexible way to determine the required module is to provide a filter
50             function. When looking for a suitable module, the function is called once for
51             each candidate module, and is passed the module's name and the search key. The
52             function can then return a boolean to indicate whether the module will be
53             suitable. The value of the search key is not directly used by the
54             C<Module::PluginFinder> in this case, and therefore is not restricted to being
55             a simple scalar value; any sort of reference may be passed.
56              
57             Instead of a filter function, the factory can inspect a package variable or
58             constant method in each of the candidate modules, looking for a string match
59             with the search key; see the C<typevar> and C<typefunc> constructor arguments.
60             When using this construction, a map from type names to module names will be
61             cached at the time the C<Module::PluginFinder> object is created, and will
62             therefore not be sensitive to changes in the values once this is done. Because
63             of this, the key should be a simple string, rather than a reference.
64              
65             =cut
66              
67             =head1 CONSTRUCTOR
68              
69             =cut
70              
71             =head2 $finder = Module::PluginFinder->new( %args )
72              
73             Constructs a new C<Module::PluginFinder> factory object. The constructor will
74             search the module path for all available plugins, as determined by the
75             C<search_path> key and store them.
76              
77             The C<%args> hash must take the following keys:
78              
79             =over 8
80              
81             =item search_path => STRING or ARRAY
82              
83             A string declaring the module namespace, or an array reference of module
84             namespaces to search for plugins (passed to L<Module::Pluggable::Object>).
85              
86             =back
87              
88             In order to specify the way candidate modules are selected, one of the
89             following keys must be supplied.
90              
91             =over 8
92              
93             =item filter => CODE
94              
95             The filter function for determining whether a module is suitable as a plugin
96              
97             =item typevar => STRING
98              
99             The name of a package variable to match against the search key
100              
101             =item typefunc => STRING
102              
103             The name of a package method to call to return the type name. The method will
104             be called in scalar context with no arguments; as
105              
106             $type = $module->$typefunc();
107              
108             If it returns C<undef> or throws an exception, then the module will be ignored
109              
110             =back
111              
112             =cut
113              
114             sub new
115             {
116 10     10 1 14497 my $class = shift;
117 10         64 my ( %args ) = @_;
118              
119 10 100       89 my $search_path = delete $args{search_path} or croak "Expected a 'search_path' key";
120              
121 9         91 my $finder = Module::Pluggable::Object->new(
122             search_path => $search_path,
123             require => 1,
124             inner => 0,
125             );
126              
127 9         180 my $self = bless {
128             finder => $finder,
129             modules => [],
130             }, $class;
131              
132 9 100       62 if( exists $args{filter} ) {
    100          
    50          
133 4         11 my $filter = delete $args{filter};
134 4 100       47 ref $filter eq "CODE" or croak "Expected that 'filter' argument be a CODE ref";
135              
136 3         18 $self->{filter} = $filter;
137             }
138             elsif( exists $args{typevar} ) {
139 3         19 $self->{typevar} = delete $args{typevar};
140             }
141             elsif( exists $args{typefunc} ) {
142 2         10 $self->{typefunc} = delete $args{typefunc};
143             }
144             else {
145 0         0 croak "Expected a 'filter', 'typefunc' or 'typevar' argument";
146             }
147              
148 8         36 $self->rescan;
149              
150 8         95 return $self;
151             }
152              
153             =head1 METHODS
154              
155             =cut
156              
157             =head2 @modules = $finder->modules()
158              
159             Returns the list of module names available to the finder.
160              
161             =cut
162              
163             sub modules
164             {
165 12     12 1 1093 my $self = shift;
166 12         23 return @{ $self->{modules} };
  12         92  
167             }
168              
169             =head2 $module = $finder->find_module( $searchkey )
170              
171             Search for a plugin module that matches the search key. Returns the name of
172             the first module for which the filter returns true, or C<undef> if no suitable
173             module was found.
174              
175             =over 8
176              
177             =item $searchkey
178              
179             A value to pass to the stored filter function.
180              
181             =back
182              
183             =cut
184              
185             sub find_module
186             {
187 13     13 1 2687 my $self = shift;
188 13         46 my ( $searchkey ) = @_;
189              
190 13 100       58 if( exists $self->{typemap} ) {
191 9         55 return $self->{typemap}->{$searchkey};
192             }
193              
194 4         9 my $filter = $self->{filter};
195              
196 4         6 foreach my $module ( @{ $self->{modules} } ) {
  4         12  
197 15 100       108 return $module if $filter->( $module, $searchkey );
198             }
199              
200 1         11 return undef;
201             }
202              
203             =head2 $object = $finder->construct( $searchkey, @constructorargs )
204              
205             Search for a plugin module that matches the search key, then attempt to create
206             a new object in that class. If a suitable module is found to match the
207             C<$searchkey> then the C<new> method is called on it, passing the
208             C<@constructorargs>. If no suitable module is found then an exception is
209             thrown.
210              
211             =over 8
212              
213             =item $searchkey
214              
215             A value to pass to the stored filter function.
216              
217             =item @constructorargs
218              
219             A list to pass to the class constructor.
220              
221             =back
222              
223             =cut
224              
225             sub construct
226             {
227 3     3 1 8 my $self = shift;
228 3         11 my ( $searchkey, @constructorargs ) = @_;
229              
230 3         11 my $class = $self->find_module( $searchkey );
231              
232 3 50       38 return $class->new( @constructorargs ) if defined $class;
233              
234 0         0 croak "Unable to find a suitable class";
235             }
236              
237             =head2 $finder->rescan()
238              
239             Perform another search for plugin modules. This method is useful whenever new
240             modules may be present since the object was first constructed.
241              
242             =cut
243              
244             sub rescan
245             {
246 10     10 1 23 my $self = shift;
247              
248 10         20 my $finder = $self->{finder};
249              
250 10         49 @{ $self->{modules} } = $finder->plugins;
  10         60377  
251              
252 10 100       86 if( exists $self->{typevar} ) {
    100          
253 5         13 my $typevar = $self->{typevar};
254 5         10 my %typemap;
255              
256 5         25 foreach my $module ( $self->modules ) {
257 6     6   133 no strict qw( refs );
  6         20  
  6         1163  
258              
259 26 100       38 next unless defined ${$module."::".$typevar};
  26         262  
260 13         20 my $moduletype = ${$module."::".$typevar};
  13         40  
261              
262 13 100       43 if( exists $typemap{$moduletype} ) {
263 3         102 carp "Already found module '$typemap{$moduletype}' for type '$moduletype'; not adding '$module' as well";
264 3         2986 next;
265             }
266              
267 10         30 $typemap{$moduletype} = $module;
268             }
269              
270 5         20 $self->{typemap} = \%typemap;
271             }
272             elsif( exists $self->{typefunc} ) {
273 2         5 my $typefunc = $self->{typefunc};
274 2         3 my %typemap;
275              
276 2         10 foreach my $module ( $self->modules ) {
277 6     6   36 no strict qw( refs );
  6         13  
  6         1138  
278              
279 10 100       105 next unless $module->can( $typefunc );
280 7         12 my $moduletype = eval { $module->$typefunc() };
  7         25  
281 7 100       36 next unless defined $moduletype;
282              
283 6 100       17 if( exists $typemap{$moduletype} ) {
284 3         87 carp "Already found module '$typemap{$moduletype}' for type '$moduletype'; not adding '$module' as well";
285 3         2305 next;
286             }
287              
288 3         10 $typemap{$moduletype} = $module;
289             }
290              
291 2         9 $self->{typemap} = \%typemap;
292             }
293              
294 10         32 return; # Avoid implicit return-of-last-expression
295             }
296              
297             # Keep perl happy; keep Britain tidy
298             1;
299              
300             __END__
301              
302             =head1 EXAMPLES
303              
304             The filter function allows various ways to select plugin modules on different
305             criteria. The following examples indicate a few ways to do this.
306              
307             =head2 Availability of a function / method
308              
309             my $f = Module::PluginFinder->new(
310             search_path => ...,
311              
312             filter => sub {
313             my ( $module, $searchkey ) = @_;
314              
315             return $module->can( $searchkey );
316             },
317             );
318              
319             Each plugin then simply has to implement the required function or method in
320             order to be automatically selected.
321              
322             =head2 Value of a method call
323              
324             my $f = Module::PluginFinder->new(
325             search_path => ...,
326              
327             filter => sub {
328             my ( $module, $searchkey ) = @_;
329              
330             return 0 unless $module->can( "is_plugin_for" );
331             return $module->is_plugin_for( $searchkey );
332             },
333             );
334              
335             Each plugin then needs to implement a method called C<is_plugin_for>, that
336             should examine the $searchkey and perform whatever testing it requires, then
337             return a boolean to indicate if the plugin is suitable.
338              
339             =head2 Value of a constant
340              
341             Because a constant declared by the C<use constant> pragma is a plain function,
342             it can be called by the C<typefunc> filter:
343              
344             my $f = Module::PluginFinder->new(
345             search_path => ...,
346              
347             typefunc => 'PLUGIN_TYPE',
348             );
349              
350             Each plugin can then declare its type using a constuction like
351              
352             use constant PLUGIN_TYPE => "my type here";
353              
354             Alternatively, a normal package method may be created that performs any work
355             required to determine the plugin's type
356              
357             sub PLUGIN_TYPE
358             {
359             my $class = shift;
360              
361             ...
362              
363             return $typename;
364             }
365              
366             Note that the type function in each module will only be called once, and the
367             returned value cached.
368              
369             =head2 Value of a package scalar
370              
371             The C<typevar> constructor argument generates the filter function
372             automatically.
373              
374             my $f = Module::PluginFinder->new(
375             search_path => ...,
376              
377             typevar => 'PLUGIN_TYPE',
378             );
379              
380             Each plugin can then declare its type using a normal C<our> scalar variable:
381              
382             our $PLUGIN_TYPE = "my type here";
383              
384             =head1 SEE ALSO
385              
386             =over 4
387              
388             =item *
389              
390             L<Module::Pluggable> - automatically give your module the ability to have
391             plugins
392              
393             =back
394              
395             =head1 AUTHOR
396              
397             Paul Evans <leonerd@leonerd.org.uk>