File Coverage

blib/lib/Plugin/Simple.pm
Criterion Covered Total %
statement 104 108 96.3
branch 40 44 90.9
condition 5 6 83.3
subroutine 14 14 100.0
pod n/a
total 163 172 94.7


line stmt bran cond sub pod time code
1             package Plugin::Simple;
2 7     7   169317 use 5.006;
  7         54  
3 7     7   29 use strict;
  7         9  
  7         134  
4 7     7   39 use warnings;
  7         10  
  7         172  
5              
6 7     7   28 use Carp qw(croak);
  7         24  
  7         367  
7 7     7   40 use Cwd qw (abs_path);
  7         10  
  7         264  
8 7     7   2622 use Module::List qw(list_modules);
  7         129326  
  7         342  
9 7     7   3871 use Module::Load;
  7         5911  
  7         37  
10              
11             our $VERSION = '1.01';
12              
13             my $self;
14              
15             sub import {
16 10     10   1836 my ($class, %opts) = @_;
17              
18 10         31 $self = __PACKAGE__->_new(%opts);
19              
20 10 100       32 my $sub_name = $opts{sub_name} ? $opts{sub_name} : 'plugins';
21              
22             {
23 7     7   626 no warnings 'redefine';
  7         12  
  7         222  
  10         15  
24 7     7   33 no strict 'refs';
  7         10  
  7         4653  
25              
26 10         36 my $pkg = (caller)[0];
27 10         19 *{"$pkg\::$sub_name"} = \&_plugins;
  10         2555  
28             }
29             }
30             sub _new {
31 12     12   148 my ($class, %args) = @_;
32 12         33 my $self = bless \%args, $class;
33              
34 12         60 return $self;
35             }
36             sub _search {
37 6     6   1100 my ($self, $pkg, $item) = @_;
38              
39 6         7 my @plugins;
40              
41 6 100       15 if ($item){
42 3 100       12 if ($item !~ /::$/){
43 2         5 push @plugins, $item;
44             }
45             else {
46 1         1 my $candidates;
47 1         2 eval { $candidates = list_modules(
  1         4  
48             $item,
49             {list_modules => 1, recurse => 1}
50             );
51             };
52 1         3154 push @plugins, keys %$candidates;
53             }
54             }
55             else {
56 3         5 my $path = $pkg;
57 3         7 $path .= '::Plugin::';
58 3         5 my $candidates = {};
59 3         5 eval { $candidates = list_modules(
  3         18  
60             $path,
61             {
62             list_modules => 1,
63             recurse => 1
64             }
65             );
66             };
67 3         1270 push @plugins, keys %$candidates;
68             }
69              
70 6         9 my @loaded;
71              
72 6         15 for (@plugins){
73 32         58 my $ok = $self->_load($_);
74 32         86 push @loaded, $ok;
75             }
76              
77 6         24 return @loaded;
78             }
79             sub _load {
80 42     42   1477 my ($self, $plugin) = @_;
81              
82 42 100       160 if ($plugin =~ /(.*)\W(\w+)\.pm/){
    50          
83 9         28 unshift @INC, $1;
84 9         16 $plugin = $2;
85             }
86             elsif ($plugin =~ /^(\w+)\.pm$/){
87 0         0 unshift @INC, '.';
88 0         0 $plugin = $1;
89             }
90              
91 42         50 my $loaded = eval { load $plugin; 1; };
  42         100  
  41         197145  
92              
93 42 100       432 if ($loaded) {
94 41         86 return $plugin;
95             }
96             }
97             sub _plugins {
98 12 100   12   6939 shift if ref $_[0]; # dump the calling object if present
99              
100 12         19 my ($item, $can);
101              
102 12 100 100     50 if ($_[0] && $_[0] eq 'can'){
103 1         2 shift;
104 1         1 $can = shift;
105             }
106             else {
107 11         19 $item = shift;
108 11         11 shift;
109 11         14 $can = shift;
110             }
111              
112 12 100       25 if (@_){
113 1         75 croak "usage: plugins(['Load::From'], [can => 'sub']), " .
114             "in that order\n";
115             }
116              
117 11         27 my $pkg = (caller)[0];
118 11         17 my @plugins;
119              
120 11 100       20 if ($item){
121 10 100       44 if ($item =~ /(?:\.pm|\.pl)/){
122 9         14 my $abs_path;
123 9         10 my $ok_file = eval { $abs_path = abs_path($item); 1 };
  9         268  
  9         20  
124              
125 9 50       22 if (! $ok_file){
126 0         0 croak
127             "\npackage $item can't be found, and no default plugin set\n";
128             }
129              
130 9 100       87 if (-e $abs_path){
131 8         27 @plugins = $self->_load($abs_path);
132             }
133             }
134             else{
135 1         4 @plugins = $self->_search($pkg, $item);
136             }
137             }
138 11 100       28 if (! @plugins){
139 2         6 @plugins = _search($pkg);
140             }
141 11 50 66     46 if (! $plugins[0] && $self->{default}){
142 0         0 push @plugins, $self->_load($self->{default});
143             }
144 11 100       23 if (! $plugins[0]){
145 3 100       8 if ($item){
146 2         249 croak
147             "\npackage $item can't be found, and no default plugin set\n";
148             }
149             else {
150 1         104 croak "\npackage can't be found, and no default plugin set\n";
151             }
152             }
153 8         10 my @wanted_plugins;
154              
155 8 100       17 if ($can) {
156 3         6 for my $mod (@plugins){
157 3         4 my $can_count = 0;
158 3         5 for my $sub (@$can){
159 4 100       17 if ($mod->can($sub)){
160 3         6 $can_count++;
161             }
162             }
163 3 100       7 push @wanted_plugins, $mod if $can_count == @$can;
164             }
165 3 50       11 return wantarray ? @wanted_plugins : $wanted_plugins[0];
166             }
167              
168 5 100       23 return wantarray ? @plugins : $plugins[0];
169             }
170              
171             1;
172              
173             =head1 NAME
174              
175             Plugin::Simple - Load plugins from files or modules.
176              
177             =for html
178            
179             Coverage Status
180              
181             =head1 SYNOPSIS
182              
183             use Plugin::Simple;
184              
185             # load a plugin module from a file
186              
187             @plugins = plugins('/path/to/MyModule.pm');
188              
189             # load all modules under '__PACKAGE__::Plugin' namespace
190              
191             my @plugins = plugins(); # call in scalar context to retrieve the first one
192              
193             # load all plugins under a specific namespace (note the trailing ::)
194              
195             @plugins = plugins('Any::Namespace::');
196              
197             # load/return only the plugins that can perform specific functions
198              
199             @plugins = plugins(can => ['foo', 'bar']); # foo and bar
200              
201             # instead of importing 'plugins()', change the name:
202              
203             use Plugin::Simple sub_name => 'foo';
204             @plugins = foo(...);
205              
206             # set a default fallback plugin if searching turns up nothing
207              
208             use Plugin::Simple default => 'My::Module::Plugin::DefaultPlugin'
209              
210             # do something with the plugins
211              
212             for my $plugin (@plugins){
213             $plugin->plugin_func(@args);
214             }
215              
216             # works in OO modules too simply by using it
217              
218             my @plugins = $self->plugins();
219              
220             =head1 DESCRIPTION
221              
222             There are many plugin modules available on the CPAN, but I wrote this one just
223             for fun. It's very simple, extremely lightweight, and is extremely minimalistic
224             in what it does.
225              
226             It searches for modules in installed packages or non-installed files, and loads
227             them (without string C). You can optionally have us return only the
228             plugins that C perform a specific task.
229              
230             =head1 LOAD OPTIONS
231              
232             By default, we force C into your namespace. To change this name:
233              
234             use Plugin::Simple sub_name => 'other_name';
235              
236             If searching fails, you can ensure a default known plugin gets loaded:
237              
238             use Plugin::Simple default => 'My::Plugin';
239              
240             To use both options, simply separate them with a comma.
241              
242             =head1 FUNCTIONS/METHODS
243              
244             None. We simply install a C function within the namespace of the
245             package that Cd us.
246              
247             =head1 EXAMPLE
248              
249             This example simply uses a single plugin module with a C
250             function. In the script, we load this file, and check to ensure the plugin does
251             in fact have that sub available.
252              
253             We then call the plugins in a loop (even though in this case there's only one),
254             and send in an argument for the plugin to do work on.
255              
256             =head2 Script
257              
258             use warnings;
259             use strict;
260              
261             use lib '.';
262              
263             use Plugin::Simple;
264              
265             my @plugins = plugins(
266             'examples/TestPlugin.pm',
267             can => ['plugin_function']
268             );
269              
270             my $plugin_arg = 'Hello!';
271              
272             for my $plugin (@plugins){
273             $plugin->plugin_function($plugin_arg);
274             }
275              
276             =head2 Plugin Module
277              
278             package TestPlugin;
279              
280             sub plugin_function {
281             shift; # throw away class/obj
282             my ($str) = @_;
283             print "in " . __PACKAGE__ . ", arg is: $str\n";
284             }
285              
286             1;
287              
288             =head2 Output
289              
290             in TestPlugin, arg is: Hello!
291              
292             =head1 AUTHOR
293              
294             Steve Bertrand, C<< >>
295              
296             =head1 BUGS
297              
298             L
299              
300             =head1 SEE ALSO
301              
302             There are far too many plugin import modules on the CPAN to mention here.
303              
304             =head1 LICENSE AND COPYRIGHT
305              
306             Copyright 2016,2017,2018 Steve Bertrand.
307              
308             This program is free software; you can redistribute it and/or modify it
309             under the terms of either: the GNU General Public License as published
310             by the Free Software Foundation; or the Artistic License.
311              
312             See L for more information.
313