File Coverage

lib/Module/List/Pluggable.pm
Criterion Covered Total %
statement 111 140 79.2
branch 14 24 58.3
condition 2 6 33.3
subroutine 14 16 87.5
pod 8 8 100.0
total 149 194 76.8


line stmt bran cond sub pod time code
1             package Module::List::Pluggable;
2              
3             =head1 NAME
4              
5             Module::List::Pluggable - list or require sub-sets of modules
6              
7             =head1 SYNOPSIS
8              
9             use Module::List::Pluggable qw( list_modules_under import_modules );
10              
11             # get a list of all modules installed under a given point
12             # in perl's module namespace
13             my @plugins = list_modules_under( "My::Project::Plugins" );
14              
15             # require & import all modules in the tree
16             import_modules( "My::Project::Plugins::ViaExporter" );
17              
18             # skip some of them
19             import_modules( "My::Project::Plugins::ViaExporter",
20             { exceptions =>
21             'My::Project::Plugins::ViaExporter::ButNotThese' }
22             );
23              
24             # just require them, don't do an "import"
25             import_modules( "My::Project::Plugins::ViaExporter",
26             { import => 0 }
27             );
28              
29             =head1 DESCRIPTION
30              
31             This module provides some procedural routines to
32              
33             (1) list a sub-set of modules installed in a particular place in
34             perl's module namespace,
35              
36             (2) require those modules and import their exported features into
37             the current package.
38              
39             Both of these functions are useful for implementing "plug-in"
40             extension mechanisms.
41              
42             Note: this module is named Module::List::Pluggable because it
43             uses L to do some things similar to L.
44              
45             =head2 EXPORT
46              
47             None by default. The following are exported on request
48             (":all" tag is available that brings in all of them):
49              
50             =over
51              
52             =cut
53              
54 1     1   94759 use 5.8.0;
  1         5  
  1         43  
55 1     1   6 use strict;
  1         1  
  1         23  
56 1     1   5 use warnings;
  1         5  
  1         37  
57 1     1   1167 use Module::List qw(list_modules);
  1         33320  
  1         69  
58 1     1   11 use Carp qw(carp croak);
  1         2  
  1         46  
59 1     1   5 use Data::Dumper;
  1         2  
  1         787  
60              
61             require Exporter;
62             our @ISA = qw(Exporter);
63             our %EXPORT_TAGS = ( 'all' => [ qw(
64             list_modules_under
65             import_modules
66             require_modules
67             list_exports
68             report_export_locations
69             check_plugin_exports
70             ) ] );
71             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
72             our @EXPORT = qw(
73             );
74              
75             our $VERSION = '0.08';
76              
77             =item list_modules_under
78              
79             Uses the "list_modules" feature of Module::List to get
80             a list of all modules under the given root location
81             in perl's module namespace.
82              
83             Example:
84             my @plugins = list_modules_under( "My::Project::Plugins" );
85              
86             Note that if no location is supplied, this will list all
87             installed modules: this can take some time.
88              
89             =cut
90              
91             sub list_modules_under {
92 15   50 15 1 2956 my $root = shift || '';
93 15         31 my $opts = shift;
94 15         617 my $plugin_exceptions = $opts->{ exceptions };
95              
96 15 50       46 unless( defined($root) ){
97 0         0 croak "list_modules_under: missing argument, need a module prefix";
98             }
99             # Module::List insists on a trailing "::" on the given prefix
100             # let's make that optional (if it isn't there, append it, unless
101             # the string is blank to start with)
102             $root =~
103 15 50       465 s/($root)/$1::/
104             unless $root =~ m{(?: :: $)|(?: ^ \s* $ )}x;
105              
106             # using the Module::List routine
107 15         124 my $modules = list_modules("$root",
108             { list_modules =>1, recurse => 1});
109              
110             # but list_modules returns an href, so let's make that a list
111 15         24326 my @found = keys %{ $modules };
  15         91  
112              
113 15         34 my @results = ();
114              
115 15 100       53 if ( $plugin_exceptions->[0] ) {
116 3         5 my @pass;
117 3         9 foreach my $module (@found) {
118 12         14 foreach my $exception (@{ $plugin_exceptions }){
  12         22  
119 12 100       43 push @pass, $module unless $module eq $exception;
120             }
121 12         32 @results = @pass;
122             }
123             } else {
124 12         28 @results = @found;
125             }
126 15         82 return \@results;
127             }
128              
129              
130             =item import_modules
131              
132             Does a "require" and then an "import" of all of the modules in
133             perl's module namespace at or under the given "root" location.
134              
135             With Exporter based plugins, everything listed in @EXPORT
136             becomes available in the calling namespace.
137              
138             Inputs:
139              
140             =over
141              
142             =item location in module namespace (e.g. "My::Project::Plugins")
143              
144             =item options hash:
145              
146             =over
147              
148             =item "exceptions"
149              
150             list of exceptions, modules to be skipped (aref)
151              
152             =item "beware_conflicts"
153              
154             if true, errors out if a conflict is discovered (i.e., the
155             same name imported twice from different plug-ins). defaults
156             to 1, set to 0 if you don't want to worry about this
157             (perhaps for efficiency reasons?)
158              
159             =back
160              
161             =back
162              
163             Returns: the number of successfully loaded modules.
164              
165             =cut
166              
167             sub import_modules {
168 5     5 1 15293 my $root = shift;
169 5         12 my $opts = shift;
170 5         15 my $exceptions = $opts->{ exceptions };
171 5   50     44 my $beware_conflicts = $opts->{ beware_conflicts } || 1;
172              
173 5 50       38 if ( $root =~ m{ ^ \s* $ }x ) {
174 0         0 croak "import_modules called without a plugin root location";
175             }
176              
177             # check for multiple plugin exports with the same name
178             # (also checks for syntax errors in plugin modules)
179 5 50       34 check_plugin_exports( $root,
180             { exceptions => $exceptions,
181             } ) if ( $beware_conflicts );
182              
183 3         16 my $plugins
184             = list_modules_under( $root,
185             { exceptions => $exceptions,
186             } );
187              
188 3         8 my ($eval_code, $error_prefix);
189 3         7 my $count = 0;
190 3         5 foreach my $plugin (@{ $plugins }) {
  3         8  
191 5         15 $error_prefix = "import_modules: $plugin: ";
192              
193 5         14 my $calling_namespace = caller(0);
194 5         24 $eval_code =
195             "package $calling_namespace; " .
196             "require $plugin; ".
197             "import $plugin;" ;
198              
199 5         10 $error_prefix = "import_modules: $plugin: ";
200 5         13 run_code_or_die( $eval_code, $error_prefix );
201 5         12 $count++; # count each successfully loaded module
202             }
203 3         15 return $count;
204             }
205              
206              
207             =item require_modules
208              
209             Like "import_modules", this does a "require" (but no "import")
210             on all of the modules in perl's module namespace at or under
211             the given "root" location.
212              
213             Inputs:
214              
215             =over
216              
217             =item location in module namespace (e.g. "My::Project::Plugins")
218              
219             =item options hash:
220              
221             =over
222              
223             =item "exceptions"
224              
225             list of exceptions, modules to be skipped (aref)
226              
227             =item "beware_conflicts"
228              
229             If true, errors out if a conflict is discovered (i.e., the
230             same name imported twice from different plug-ins). Defaults
231             to 1. Set this to 0 if you don't want it to worry about this
232             (perhaps for efficiency reasons?)
233              
234             =back
235              
236             =back
237              
238             Returns: the number of successfully loaded plug-in modules.
239              
240             =cut
241              
242             sub require_modules {
243 0     0 1 0 my $root = shift;
244 0         0 my $opts = shift;
245 0         0 my $exceptions = $opts->{ exceptions };
246 0   0     0 my $beware_conflicts = $opts->{ beware_conflicts } || 1;
247              
248 0 0       0 if ( $root =~ m{ ^ \s* $ }x ) {
249 0         0 croak "require_modules called without a plugin root location";
250             }
251              
252             # check for multiple plugin exports with the same name
253             # (also checks for syntax errors in plugin modules)
254 0 0       0 check_plugin_exports( $root,
255             { exceptions => $exceptions,
256             } ) if ( $beware_conflicts );
257              
258 0         0 my $plugins
259             = list_modules_under( $root,
260             { exceptions => $exceptions,
261             } );
262              
263 0         0 my ($eval_code, $error_prefix);
264 0         0 my $count = 0;
265 0         0 foreach my $plugin (@{ $plugins }) {
  0         0  
266 0         0 $error_prefix = "require_modules: $plugin: ";
267              
268 0         0 $eval_code =
269             "require $plugin";
270              
271 0         0 run_code_or_die( $eval_code, $error_prefix );
272 0         0 $count++; # count each successfully loaded module
273             }
274 0         0 return $count;
275             }
276              
277             =back
278              
279             =head2 reporting routines
280              
281             =over
282              
283             =item list_exports
284              
285             Returns a list (aref) of all items that are exported from the
286             modules under the object's plugin root.
287              
288             =cut
289              
290             sub list_exports {
291 1     1 1 542 my $root = shift;
292 1         5 my $modules = list_modules_under( $root );
293              
294 1         3 my @list = ();
295 1         3 foreach my $mod ( @{ $modules } ) {
  1         3  
296 1         3 my $export_list = $mod . '::EXPORT';
297             {
298 1     1   7 no strict 'refs';
  1         1  
  1         178  
  1         2  
299 1         2 push @list, @{ $export_list };
  1         8  
300             }
301             }
302 1         5 return \@list;
303             }
304              
305             =item report_export_locations
306              
307             Reports on all routines that are exported by the modules
308             under the object's plug-in root, including the module
309             where each routine is found.
310              
311             Inputs:
312              
313             =over
314              
315             =item The location to begin scanning in module name space,
316             e.g. "Mah::Modules::Plugins"
317              
318             =item An options hash reference, with options:
319              
320             =over
321              
322             =item exceptions
323              
324             And array reference of plug-in modules to be ignored.
325              
326             =back
327              
328             =back
329              
330             Return:
331              
332             A hash reference, keyed by the names of the exported routines
333             with values that are array references listing all modules
334             where that routine was found.
335              
336             =cut
337              
338             sub report_export_locations {
339 9     9 1 2851 my $root = shift;
340 9         15 my $opts = shift;
341 9         19 my $plugin_exceptions = $opts->{ exceptions };
342              
343 9         36 my $modules = list_modules_under( $root,
344             { exceptions => $plugin_exceptions });
345              
346 9         50 my $report = {};
347 9         16 foreach my $mod ( @{ $modules } ) {
  9         28  
348              
349 22         56 my $error_prefix = "report_export_locations: $mod: ";
350 22         42 my $eval_code =
351             "require $mod";
352              
353 22         59 run_code_or_die( $eval_code, $error_prefix );
354              
355 21         50 my $export_array = $mod . '::EXPORT';
356             {
357 1     1   4 no strict 'refs';
  1         2  
  1         415  
  21         144  
358 21         30 my @exports = @{ $export_array };
  21         99  
359 21         85 foreach my $ex (@exports) {
360 42         47 push @{ $report->{ $ex } }, $mod;
  42         192  
361             }
362             }
363             }
364 8         32 return $report;
365             }
366              
367             =back
368              
369             =head2 routines primarily for internal use
370              
371             =over
372              
373             =item check_plugin_exports
374              
375             Looks for conflicts in the tree of plug-ins under the given plug-in root.
376             Errors out if it finds multiple definitions of exported items
377             of the same names.
378              
379             The form of the error message is:
380              
381             Multiple definitions of ___ from plugins: ___
382              
383             Inputs:
384              
385             =over
386              
387             =item the location to begin scanning in module name space,
388             e.g. "Mah::Modules::Plugins"
389              
390             =item an options hash reference, with options:
391              
392             =over
393              
394             =item exceptions
395              
396             array reference of plug-in modules to be ignored.
397              
398             =back
399              
400             =back
401              
402             Note: this routine also checks that each plug-in module is
403             free of syntax errors.
404              
405             =cut
406              
407             sub check_plugin_exports {
408 7     7 1 4177 my $root = shift;
409 7         14 my $opts = shift;
410 7         15 my $plugin_exceptions = $opts->{ exceptions };
411              
412 7         31 my $report = report_export_locations($root,
413             { exceptions => $plugin_exceptions,
414             });
415 6         22 foreach my $exported_item ( %{ $report } ){
  6         24  
416 44         172 my $aref = $report->{ $exported_item };
417 44 100       89 my @sources = @{ $aref } if defined($aref);
  23         192  
418              
419 44         56 my $count = scalar( @sources );
420              
421 44 100       117 if ($count >= 2) {
422 2         616 croak("Multiple definitions of $exported_item from plugins: " .
423             join " ", @sources );
424             }
425             }
426 4         21 return 1;
427             }
428              
429             =item run_code_or_warn
430              
431             Runs code passed in as a string (not a coderef), so that
432             "barewords" can be created from variables.
433              
434             Returns the value of the code expression.
435              
436             Generates an error message string using an optional
437             passed-in prefix, but with the the value from $@ appended.
438              
439             As with carp, the error is reported as occurring in the calling
440             context, but also includes the full error message with it's own
441             location indicated. The error message is reported to STDERR,
442             but execution continues.
443              
444             Inputs:
445              
446             =over
447              
448             =item code string
449              
450             =item prefix (optional) pre-pended to error messages.
451              
452             =back
453              
454             Example:
455              
456             my $prefix = "problem with $module_name";
457             my $code = "require $module_name";
458             run_code_or_warn( $code, $prefix );
459              
460             =cut
461              
462             sub run_code_or_warn {
463 0     0 1 0 my $code_string = shift;
464 0         0 my $prefix = shift;
465              
466 0         0 my ($package, $filename, $line) = caller(0);
467 0         0 my $context = "in $filename at line $line: ";
468              
469 0         0 my $ret = '';
470 0         0 $ret = eval $code_string;
471 0 0       0 if ($@) {
472 0         0 my $err_mess = $prefix . $context . $@;
473 0         0 print STDERR "$err_mess\n";
474             }
475 0         0 return $ret;
476             }
477              
478             =item run_code_or_die
479              
480             Variant of run_code_or_warn that dies
481              
482             Note: reports error in the calling context, much like "croak",
483             but also includes the full error message with it's own location
484             indicated.
485              
486             =cut
487              
488             sub run_code_or_die {
489              
490 27     27 1 119 my $code_string = shift;
491 27         44 my $prefix = shift;
492              
493 27         443 my ($package, $filename, $line) = caller(0);
494 27         90 my $context = "in $filename at line $line: ";
495              
496 27         40 my $ret = '';
497 27         2136 $ret = eval $code_string;
498 27 100       3940 if ($@) {
499 1         5 my $err_mess = $prefix . $context . $@;
500 1         12 die "$err_mess";
501             }
502 26         153 return $ret;
503             }
504              
505              
506              
507              
508             1;
509              
510             =back
511              
512             =head1 DISCUSSION
513              
514             A "plug-in" architecture is a way of allowing for the behavior of
515             a system to be extended at a later date by the addition of new
516             modules without any changes to the existing code.
517              
518             =head2 Plug-in Extension Techniques (polymorphism vs. promiscuity)
519              
520             There are essentially two styles of plug-ins:
521              
522             =over
523              
524             =item polymorphic plug-ins
525              
526             With "polymorphic plug-ins" a particular module appropriate to a
527             task is selected from the available set. The same set of methods
528             (often called the "interface") are defined in different ways,
529             depending on the plug-in used.
530              
531             =item promiscuous plug-ins
532              
533             With "promiscuous plug-ins", the entire set of plug-in modules
534             is used at once, and each plug-in defines new methods.
535              
536             =back
537              
538             When implementing "polymorphic plug-ins", it's often convenient to
539             get a list of available modules, and then choose one of them
540             somehow (often by applying a naming convention). The
541             "list_modules_under" routine here is helpful for this, though
542             admittedly, it's frequently almost as easy to just require the
543             expected module, and trap the error if the module doesn't exist.
544              
545             For "promiscuous plug-ins", there are essentially two sub-types,
546             object-oriented and procedural. In the object-oriented case, a
547             list of modules can be pushed directly into the @ISA array so that
548             any methods implemented in the extension modules become available
549             via the justly-feared but occasionally useful
550             "multiple-inheritance" mechanism. In the procedural case, you
551             can use the "import_modules" routine provided here, which does
552             something like a use-at-runtime on all of the plug-ins (it does a
553             "require" of each module, and then an "import").
554              
555             Obviously, in the object-oriented form, the routines in the
556             extensions must be written as methods (e.g. each should begin
557             with "my $self=shift;"). In the procedural case, each module
558             should use "Exporter", and to work with the "import_modules"
559             routine supplied here, all features to-be-exported should be in
560             the @EXPORT array of each plug-in module.
561              
562             But note that these two approaches can be combined into a hybrid
563             form: Exporter can be used to bring a collection of OOP methods
564             into the current object's namespace.
565              
566             These Exporter-based "promiscuous plug-ins" (whether OOP or
567             procedural) have an advantage over the multiple-inheritance
568             approach: the damage is limited that can be done by the addition
569             of a new, perhaps carelessly written plug-in module:
570             The "import_modules" routine (by default) watches for name
571             collisions in the routines imported from the plug-ins, and
572             throws an error when they occur. In comparison the simple MI
573             solution will silently use which ever plug-in method it sees
574             first in the path; so if the sort order of your list of plug-in
575             modules doesn't work as a precedence list, then you may be in
576             trouble.
577              
578             Using the hybrid approach (OOP methods brought into a common
579             namespace by using "import_modules"), you need to watch out for
580             the fact that these plug-in methods will inherit based on the
581             @ISA of the class they're imported into: a "use base" in the
582             package where the methods are defined will have no effect. If
583             your plug-ins all need to use common code inherited from a
584             particular module, then the parent needs to be in the
585             inheritance chain of the class the plug-ins are imported into,
586             not in the package in which they were originally written.
587              
588             A restriction that all "promiscuous" OOP plug-in schemes share
589             (to my knowledge) is that sub-classing essentially doesn't work
590             with them. Simply adding a subclass of a plug-in to the set is
591             not enough to reliably override the original: the precedence
592             between the two will be silently chosen based on some arbitrary
593             criteria (typically accidents of sort order in the module
594             names).
595              
596             Even if a way could be found to solve that problem (e.g. an
597             import mechanism that skips parents when a child exists) it
598             wouldn't seem advisable to use it: simply adding a new plug-in
599             would have the potential to break existing code.
600              
601             However, if you *really* feel the need to do something like this,
602             the "exceptions" feature of "import_modules" could be used to
603             manually suppress a parent plug-in, so that only the child
604             plug-in will be imported. Similarly, if you realize that someone
605             else's module is creating problems for you, the "exceptions"
606             feature provides an alternate way to suppress it's use without
607             uninstalling it.
608              
609              
610             =head1 MOTIVATION
611              
612             The L project is an example of a use of
613             "promiscuous plug-ins" to provide an extreme (perhaps
614             "pathological") degree of extensibility.
615              
616             =head2 list_modules_under
617              
618             The wrapper routine "list_modules_under" seemed advisable
619             because of the very clunky interface of the "list_modules"
620             routine provided by L (see below). But then, at
621             least Module::List actually works correctly, unlike
622             L (which double-reports modules if found in two
623             places in @INC). And L is peculiarly limited
624             in that it essentially assumes you'll have a hardcoded search
625             location in module namespace.
626              
627             =head2 Module::List peculiarities
628              
629             The list_modules routine exported by Module::List has two
630             "options" that you will almost always want enabled:
631              
632             my $modules = list_modules("$root",
633             { list_modules =>1, recurse => 1});
634              
635             You need to tell list_modules that you really want it to list the
636             modules (reminiscent of the "-print" option on the original unix
637             "find" command). But recursion is I by default, the
638             opposite of the "find" convention...
639              
640             And the return value from "list_modules" is a hash reference
641             (not a 'list' or an aref). What you actually want is the keys
642             of this hash (the values are just undefs):
643              
644             my @found = keys %{ $modules };
645              
646             Another minor irritation is that the first argument (a place in
647             module name space) is required to have a trailing "::" appended
648             to it. However, it does understand that an empty string should
649             be interpreted as the entire list of installed modules (note: it
650             takes a long time to get this full list, as you might expect).
651              
652             =head1 LIMITATIONS
653              
654             When using "import_modules" (which brings in methods via Exporter):
655              
656             o methods can inherit from the @ISA of their new context, but
657             not the package they came from.
658              
659             o subclassing an existing plug-in to create a new one should
660             almost always be avoided: the precedence of child over
661             parent can't be easily guaranteed, and adding a new plug-in
662             can break existing code.
663              
664             =head1 SEE ALSO
665              
666             L
667             L
668             L
669              
670             =head1 TODO
671              
672             Add a "recurse" option to both routines: default to recurse, but
673             allow them to work on a single directory level.
674              
675             =head1 AUTHOR
676              
677             Joseph Brenner, Edoom@E
678              
679             =head1 COPYRIGHT AND LICENSE
680              
681             Copyright (C) 2007 by Joseph Brenner
682              
683             This library is free software; you can redistribute it and/or modify
684             it under the same terms as Perl itself, either Perl version 5.8.7 or,
685             at your option, any later version of Perl 5 you may have available.
686              
687              
688             =cut