File Coverage

blib/lib/Plugins.pm
Criterion Covered Total %
statement 105 208 50.4
branch 23 94 24.4
condition 15 30 50.0
subroutine 20 32 62.5
pod 16 20 80.0
total 179 384 46.6


line stmt bran cond sub pod time code
1             # Copyright (C) 2006-2007, David Muir Sharnoff
2              
3             package Plugins;
4              
5 1     1   11 use strict;
  1         1  
  1         37  
6 1     1   5 use warnings;
  1         2  
  1         29  
7 1     1   1751 use UNIVERSAL qw(can);
  1         15  
  1         5  
8 1     1   474 use Carp;
  1         2  
  1         1272  
9             our $VERSION = 0.41;
10             our $debug = 0;
11              
12             sub new
13             {
14 3     3 1 13 my ($pkg, %args) = @_;
15              
16 3   100     17 my $context = $args{context} || {};
17 3   100     12 my $pkg_override = $context->{pkg_override} || '';
18              
19 3 100 66     45 if ($pkg_override ne __PACKAGE__
      100        
      66        
20             and scalar(caller()) ne $pkg_override
21             and can($pkg_override, 'new')
22             and can($pkg_override, 'new') ne \&new)
23             {
24 1         4 my $new = can($pkg_override, 'new');
25 1 50       5 croak "no new in $pkg_override" unless $new;
26 1         4 @_ = ($pkg_override, %args);
27 1         6 goto &$new; # so caller() works
28             }
29              
30 2   66     52 my $self = bless {
      50        
31             %args,
32             list => undef,
33             new_list => undef,
34             plugins => {},
35             new_config => undef,
36             config => {},
37             configfile => $args{configfile} || $context->{configfile},
38             context => $context,
39             requestor => $args{requestor} || scalar(caller()),
40             api => $args{api},
41             }, $pkg;
42              
43 2         9 return $self;
44             }
45              
46             sub startconfig
47             {
48 2     2 1 4 my ($self) = @_;
49              
50 2         5 $self->{new_list} = [];
51 2         3 $self->{new_config} = {};
52             }
53              
54             sub readconfig
55             {
56 2     2 1 21 my ($self, $configfile, %args) = @_;
57              
58 2 50       8 croak "only one call to readconfig() before initialize()" if $self->{new_list};
59              
60 2         11 $self->startconfig();
61 2   50     6 $args{self} ||= scalar(caller());
62 2         13 $self->parseconfig($configfile, %args);
63             }
64              
65 0     0 1 0 sub parseconfig { croak "Plugins must be subclassed and the subclass must define a parseconfig() method"; };
66              
67             our %required;
68              
69             sub pkg_invoke
70             {
71 8     8 1 18 my ($self, $pkg, $method, @args) = @_;
72 8 100       21 unless ($required{$pkg}++) {
73 1         2 my $p = $pkg;
74 1         7 $p =~ s!::!/!g;
75 1         2 eval { require "$p.pm" };
  1         1018  
76 1 50       1370 die "require $p: $@" if $@;
77             }
78 8 50       30 return undef unless $method;
79 0         0 my $f = can($pkg, $method);
80 0 0       0 return undef unless $f;
81 0         0 return &$f(@args);
82             }
83              
84             my %used;
85              
86             #
87             # Plugins that aren't modules and need to be automagically
88             # turned into a module...
89             #
90             sub file_plugin
91             {
92 0     0 1 0 my ($self, $file, %opts) = @_;
93              
94 0 0       0 unless (-f $file) {
95 0   0     0 $opts{search_path} ||= [];
96 0         0 for my $dir (@{$opts{search_path}}) {
  0         0  
97 0 0       0 next unless -f "$dir/$file";
98 0         0 $file = "$dir/$file";
99 0         0 last;
100             }
101             }
102 0   0     0 my $ref = $opts{referenced} || '';
103 0 0       0 croak "Could not open $file $ref"
104             unless -f $file;
105 0         0 my $pkg = $file;
106 0         0 $pkg =~ s/[^A-Z0-9a-z_]//g;
107 0         0 $pkg = "Plugins::AutoGenerated::$pkg";
108 0         0 while($used{$pkg}++) {
109 0         0 $pkg .= "::R".int(rand(10000));
110             }
111              
112              
113 0         0 require File::Slurp;
114 0         0 import File::Slurp;
115              
116 0         0 my $contents = read_file($file);
117 0         0 my $justbefore = "\n#line 0 $file\n";
118              
119             # untaint
120 0         0 $contents =~ m/^(.*)/s;
121 0         0 $contents = $1;
122              
123 0   0     0 $opts{prefile} ||= '';
124 0   0     0 $opts{postfile} ||= '';
125              
126 0   0     0 my $isa = $opts{isa} || 'Plugins::Plugin';
127              
128 0         0 my $eval = sprintf("#line %d %s\n", __LINE__, __FILE__) . <
129              
130             package $pkg;
131              
132             our \@ISA = qw($isa);
133             use strict;
134              
135             $opts{prefile}
136              
137             $justbefore
138             $contents
139              
140             $opts{postfile}
141             1;
142             END
143              
144 0         0 eval $eval;
145 0 0       0 die "eval $@" if $@;
146 0         0 return $pkg;
147             }
148              
149             sub genkey
150             {
151 0     0 1 0 my ($self, $context) = @_;
152 0         0 my $key = "$context->{pkg}/$context->{configfile}";
153 0         0 return $key;
154             }
155              
156             sub registerplugin
157             {
158 4     4 1 40 my ($self, %context) = @_;
159 4         8 my $pkg = $context{pkg};
160             {
161 1     1   7 no strict qw(refs);
  1         3  
  1         733  
  4         5  
162 4         16 $self->pkg_invoke($pkg)
163 4 50       6 unless %{"${pkg}::"};
164             }
165 4         16 my $key = $self->genkey(\%context);
166 4 50       17 $context{requestor} = $self->{requestor} unless $context{requestor};
167 4 50       12 croak "Duplicate registration of $pkg plugin at $context{file}:$context{lineno} and $self->{new_config}{$key}{file}:$self->{new_config}{$key}{lineno}\n"
168             if $self->{new_config}{$key};
169 4         12 $self->{new_config}{$key} = \%context;
170 4         7 push(@{$self->{new_list}}, $key);
  4         7  
171 4         16 return \%context;
172             }
173              
174             sub initialize
175             {
176 2     2 0 16 my ($self, %args) = @_;
177              
178 2 50       9 confess "readconfig() not called yet" unless defined $self->{new_list};
179              
180 2 50       7 if ($self->{list}) {
181 0         0 my @shutargs;
182 0 0       0 @shutargs = @{$args{shutdown_args}} if $args{shutdown_args};
  0         0  
183 0         0 for my $old (@{$self->{list}}) {
  0         0  
184 0         0 $self->{plugins}{$old}->shutdown();
185 0         0 delete $self->{plugins}{$old};
186             }
187             }
188              
189 2         5 $self->{config} = $self->{new_config};
190 2         4 $self->{new_config} = undef;
191 2         6 $self->{list} = $self->{new_list};
192 2         3 $self->{new_list} = undef;
193              
194 2         3 for my $key (@{$self->{list}}) {
  2         5  
195 4         20 $self->{plugins}{$key} = $self->initialize_plugin($self->{config}{$key});
196             }
197             }
198              
199 0     0 1 0 sub post_initialize { }
200              
201             sub api
202             {
203 1     1 1 9 my ($self, $new) = @_;
204 1         2 my $old = $self->{api};
205 1 50       6 $self->{api} = $new if @_ > 1;
206 1         3 return $old;
207             }
208              
209             sub initialize_plugin
210             {
211 4     4 1 7 my ($self, $context) = @_;
212 4         8 my $pkg = $context->{pkg};
213 4 50       17 $context->{pkg_override} = ref($self)
214             unless $context->{pkg_override};
215 4 50       20 my $new = can($pkg, 'new')
216             or confess "no new() method for $pkg. \@ISA for $pkg should include Plugins::Plugin";
217 4 50       15 my $p = &$new($pkg, { context => $context, api => $self->{api} }, @{$context->{new_args}})
  4         21  
218             or confess "$pkg->new() returned false";
219 4         33 $self->post_initialize($context, $p);
220 4         20 return $p;
221             }
222              
223             sub addplugin
224             {
225 0     0 1 0 my ($self, %context) = @_;
226 0         0 my $pkg = $context{pkg};
227             {
228 1     1   6 no strict qw(refs);
  1         1  
  1         1006  
  0         0  
229 0         0 $self->pkg_invoke($pkg)
230 0 0       0 unless %{"${pkg}::"};
231             }
232 0         0 my $key = $self->genkey(\%context);
233 0 0       0 if ($self->{plugins}{$key}) {
234 0         0 $self->{plugins}{$key}->shutdown();
235             } else {
236 0         0 push(@{$self->{list}}, $key);
  0         0  
237             }
238 0 0       0 $context{requestor} = $self->{requestor} unless $context{requestor};
239 0         0 $self->{config}{$key} = \%context;
240 0         0 $self->{plugins}{$key} = $self->initialize_plugin(\%context);
241             }
242              
243             sub invoke
244             {
245 3     3 1 39 my ($self, $method, @args) = @_;
246 3 50       9 confess "readconfig() not called yet" unless defined $self->{list};
247 3 50       8 confess if $method =~ /::/;
248 3         6 for my $pkg (@{$self->{list}}) {
  3         10  
249 7         465 my $plugin = $self->{plugins}{$pkg};
250 7         29 $plugin->invoke($method, @args);
251             }
252             }
253              
254             sub invoke_until
255             {
256 0     0 1 0 my ($self, $method, $satisfied, @args) = @_;
257 0 0       0 confess "readconfig() not called yet" unless defined $self->{list};
258 0         0 for my $plugin ($self->plugins) {
259 0         0 my @r;
260 0         0 my $m = $plugin->can($method);
261 0         0 my $pkg = ref($plugin);
262 0 0       0 print STDERR "invoke_until $method on $pkg...\n" if $debug;
263 0 0       0 next unless $m;
264 0 0       0 if (wantarray) {
265 0         0 @r = eval { &$m($plugin, @args); };
  0         0  
266             } else {
267 0         0 $r[0] = eval { &$m($plugin, @args); };
  0         0  
268             }
269 0 0       0 print STDERR " results = @r\n" if $debug;
270 0 0       0 warn $@ if $@;
271 0 0       0 if (&$satisfied(@r)) {
272 0 0       0 print STDERR " satisfied!\n" if $debug;
273 0 0       0 return @r if wantarray;
274 0         0 return $r[0];
275             }
276 0 0       0 print STDERR " NOT satisfied!\n" if $debug;
277             }
278 0 0       0 return () if wantarray;
279 0         0 return undef;
280             }
281              
282              
283             sub plugins
284             {
285 1     1 1 16 my ($self) = @_;
286 1 50       6 confess "readconfig() not called yet" unless defined $self->{list};
287 1         2 return map { $self->{plugins}{$_} } @{$self->{list}};
  3         14  
  1         2  
288             }
289              
290             sub iterator
291             {
292 0     0 1 0 my ($self, $method) = @_;
293 0 0       0 confess "readconfig() not called yet" unless defined $self->{list};
294 0         0 my @plugins = @{$self->{list}};
  0         0  
295             return sub {
296 0     0   0 for (;;) {
297 0 0       0 return () unless @plugins;
298 0         0 my $plugin = shift(@plugins);
299 0         0 my $f = $self->{plugins}{$plugin}->can($method);
300 0 0       0 next unless $f;
301 0         0 return &$f($self->{plugins}{$plugin}, @_);
302             }
303             }
304 0         0 }
305              
306              
307             package Plugins::Plugin;
308              
309 1     1   7 use strict;
  1         2  
  1         33  
310 1     1   6 use warnings;
  1         2  
  1         51  
311 1     1   5 use Carp qw(cluck confess);
  1         2  
  1         439  
312              
313             our $AUTOLOAD;
314              
315 0     0   0 sub DESTROY {}
316 0     0 0 0 sub shutdown {}
317              
318             sub invoke
319             {
320 22     22 0 3335 my ($self, $method, @args) = @_;
321 22 50       48 if ($Plugins::debug) {
322 0         0 my $pkg = ref($self);
323 0         0 print STDERR "Invoking $method on $pkg\n";
324             }
325 22 50       53 confess if $method =~ /::/;
326 22         55 my $m = $self->can($method);
327 22 50       47 return undef unless $m;
328 22         75 &$m($self, @args);
329             }
330              
331             sub new
332             {
333 0     0 0   my ($pkg, $pconfig, %args) = @_;
334 0           return bless { context => $pconfig->{context}, api => $pconfig->{api}, config => \%args }, $pkg;
335             }
336              
337             sub AUTOLOAD
338             {
339 0     0     my $self = shift;
340              
341 0           my $auto = $AUTOLOAD;
342 0           my $ref = ref($self);
343 0           my $p = __PACKAGE__;
344 0 0         $auto =~ s/^${ref}::// or $auto =~ s/^${p}:://;
345 0 0         return $self->{myapi}->invoke($auto, @_)
346             if $self->{myapi};
347 0 0         return $self->{api}->invoke($auto, @_)
348             if $self->{api};
349 0           cluck "No method '$auto'";
350             }
351              
352             1;
353