File Coverage

blib/lib/WWW/Mechanize/Pluggable.pm
Criterion Covered Total %
statement 111 123 90.2
branch 25 38 65.7
condition 3 12 25.0
subroutine 26 27 96.3
pod 7 7 100.0
total 172 207 83.0


line stmt bran cond sub pod time code
1             package WWW::Mechanize::Pluggable;
2 11     11   429299 use strict;
  11         30  
  11         629  
3 11     11   16066 use WWW::Mechanize;
  11         2710752  
  11         873  
4 11     11   33234 use Data::Dump::Streamer;
  11         876000  
  11         110  
5 11     11   2129 use Carp qw(croak);
  11         25  
  11         820  
6              
7 11         112 use Module::Pluggable search_path => [ qw(WWW::Mechanize::Plugin) ],
8 11     11   7585 'require' => 1;
  11         145890  
9              
10             our $AUTOLOAD;
11              
12             BEGIN {
13 11     11   1477 use vars qw ($VERSION);
  11         25  
  11         507  
14 11     11   17942 $VERSION = "1.12";
15             }
16              
17             =head1 NAME
18              
19             WWW::Mechanize::Pluggable - A WWW::Mechanize that's custmomizable via plugins
20              
21             =head1 SYNOPSIS
22              
23             use WWW::Mechanize::Pluggable;
24             # plugins now automatically loaded
25              
26             =head1 DESCRIPTION
27              
28             This module provides all of the same functionality of C, but
29             adds support for I using C; this means that
30             any module named C> will
31             be found and loaded when C is loaded.
32              
33             Big deal, you say. Well, it I a big deal in conjunction with
34             C's other feature: I. When plugins
35             are loaded, their C methods can call C's
36             C and C methods. These methods add callbacks to the
37             plugin code in C's methods. These callbacks can
38             act before a method or after it, and have to option of short-circuiting the
39             call to the C method altogether.
40              
41             These methods receive whatever parameters the C
42             methods received, plus a reference to the actvive C object.
43              
44             All other extensions to C are handled by the
45             plugins.
46              
47             =head1 SUBCLASSING
48              
49             Subclassing this class is not recommended; partly because the method
50             redispatch we need to do internally doesn't play well with the standard
51             Perl OO model, and partly because you should be using plugins and hooks
52             instead.
53              
54             In C, it is recommended that you extend functionality by
55             subclassing C, because there's no other way to extend the
56             class. With C support, it is easy to load another method
57             directly into C's namespace; it then appears as
58             if it had always been there. In addition, the C and C
59             methods provide a way to intercept a call and replace it with your output, or
60             to tack on further processing at the end of a standard method (or even a
61             plugin!).
62              
63             The advantage of this is in not having a large number of subclasses, all of
64             which add or alter C's function, and all of which have to be
65             loaded if you want them available in your code. With
66             C, one simply installs the desired plugins and they
67             are all automatically available when you C.
68              
69             Configuration is a possible problem area; if three different plugins all
70             attempt to replace C, only one will win. It's better to create more
71             sophisticated methods that call on lower-level ones than to alter existing
72             known behavior.
73              
74             =head1 USAGE
75              
76             See the synopsis for an example use of the base module; extended behavior is
77             documented in the plugin classes.
78              
79             =head1 BUGS
80              
81             None known.
82              
83             =head1 SUPPORT
84              
85             Contact the author at C.
86              
87             =head1 AUTHOR
88              
89             Joe McMahon
90             mcmahon@yahoo-inc.com
91              
92             =head1 COPYRIGHT
93              
94             This program is free software; you can redistribute
95             it and/or modify it under the same terms as Perl itself.
96              
97             The full text of the license can be found in the
98             LICENSE file included with this module.
99              
100              
101             =head1 SEE ALSO
102              
103             L
104              
105             =head1 CLASS METHODS
106              
107             =head2 import
108              
109             Handles the delegation of import options to the appropriate plugins.
110              
111             C loads the plugins (found via a call to C<__PACKAGE__->plugins>) using
112             C; it then calls each plugin's C method with the parameters
113             specific to it, if there are any.
114              
115             =head3 What your plugin sees
116              
117             Let's take the example
118              
119             use WWW::Mechanize::Pluggable Zonk => [foo => 1, bar => [qw(a b c)]],
120             Thud => [baz => 'quux'];
121              
122             C's import() would get called like this:
123              
124             WWW::Mechanize::Plugin::Zonk->import(foo => 1, bar => [qw(a b c)]);
125              
126             And C's import() would get
127              
128             WWW::Mechanize::Plugin::Thud->import(baz => 'quux');
129              
130             So each plugin only sees what it's supposed to.
131              
132             =cut
133              
134             sub import {
135 11     11   166 my ($class, %plugin_args) = @_;
136 11         73 foreach my $plugin (__PACKAGE__->plugins) {
137 22         8064 my ($plugin_name) = ($plugin =~ /.*::(.*)$/);
138 22 100       399 if ($plugin->can('import')) {
139 11 50       55 if (exists $plugin_args{$plugin_name}) {
140 0         0 $plugin->import( @{ $plugin_args{$plugin_name} } );
  0         0  
141             }
142             else {
143 11         52 $plugin->import();
144             }
145             }
146             }
147             }
148              
149             =head2 init
150              
151             C runs through all of the plugins for this class and calls
152             their C methods (if they exist). Not meant to be called by your
153             code; it's internal-use-only.
154              
155             C gets all of the arguments supplied to C; it can
156             process them or not as it pleases.
157              
158             =head3 What your plugin sees
159              
160             Your plugin's C gets a reference to the C object
161             plus the list of parameters supplied to the C call. This is
162             assumewd to be a set of zero or more key/value pairs.
163              
164             C can return a list of keys to be deleted from the parameter
165             hash; this allows plugins to process parameters themselves without
166             the internal C object ever seeing them. If you
167             return a null list, nothing gets deleted.
168              
169             As an example:
170              
171             my $mech = new WWW::Mechanize::Pluggable foo=>'bar';
172              
173             A plugin's C could process the C argument and return C;
174             this parameter would then be deleted from the arguments.
175              
176             =cut
177              
178             sub init {
179 12     12 1 37 my ($self, %args) = @_;
180             # call all the inits (if defined) in all our
181             # plugins so they can all set up their defaults
182 12         25 my @deletes;
183 12         111 foreach my $plugin (__PACKAGE__->plugins) {
184 24 100       24726 if ($plugin->can('init')) {
185 12         79 push @deletes, $plugin->init($self, %args);
186             }
187             }
188 12         194 @deletes;
189             }
190              
191             =head2 new
192              
193             C constructs a C object and initializes
194             its pre and port hook queues. You can add parameters to be passed to
195             plugins' C methods by adding them to this C call.
196              
197             =cut
198              
199             sub new {
200 12     12 1 2780 my ($class, %args) = @_;
201 12         36 my $self = {};
202 12         48 bless $self, $class;
203              
204              
205 12         104 $self->{PreHooks} = {};
206 12         38 $self->{PostHooks} = {};
207 12         73 my @deletes = $self->init(%args);
208              
209 12         31 local $_;
210 12         85 delete $args{$_} foreach @deletes;
211            
212              
213 12         75 $self->mech($self->_create_mech_object(\%args));
214              
215 12         92 $self;
216             }
217              
218             =head2 _create_mech_object
219              
220             Create the WWW::Mechanize object. Optional parameter '_Pluggable_mech_class'
221             specifies a different class, e.g. Test::WWW::Mechanize.
222              
223             =cut
224              
225             sub _create_mech_object {
226 12     12   30 my ($self, $args) = @_;
227              
228 12         33 my $mech_class = delete $args->{_Pluggable_mech_class};
229 12 50       52 $mech_class = 'WWW::Mechanize' if !defined($mech_class);
230 12         115 $mech_class->new(%$args);
231             }
232              
233             =head2 mech
234              
235             Returns the component C object.
236              
237             This is a simple set/get accessor; normally we'd just use L
238             to create it and forget about the details. We don't use C,
239             though, because we want the C class to have no
240             superclass (other than C).
241              
242             This is necessary because we use X (q.v.) to trap all of the calls
243             to this class so they can be pre- and post-processed before being passed on
244             to the underlying C object. If we C,
245             as is needed to make it work properly, C's C gets control
246             instead of ours, and the hooks don't work.
247              
248             =cut
249              
250             sub mech {
251 22     22 1 246791 my ($self, $mech) = @_;
252 22 100       142 $self->{Mech} = $mech if defined $mech;
253 22         116 $self->{Mech};
254             }
255              
256             =head2 _insert_hook
257              
258             Adds a hook to a hook queue. This is a utility routine, encapsulating
259             the hook queue manipulation in a single method.
260              
261             Needs the queue name, the method name of the method being hooked, and a
262             reference to the hook sub itself.
263              
264             =cut
265              
266             sub _insert_hook {
267 4     4   9 my ($self, $which, $method, $hook_sub) = @_;
268 4         6 push @{$self->{$which}->{$method}}, $hook_sub;
  4         16  
269             }
270              
271             =head2 _remove_hook
272              
273             Deletes a hook from a hook queue.
274              
275             Needs the queue name, the method name of the method being hooked, and a
276             reference to the hook sub itself.
277              
278             =cut
279              
280             sub _remove_hook {
281 1     1   468 my ($self, $which, $method, $hook_sub) = @_;
282 4         11 $self->{$which}->{$method} =
283 1 50       8 [grep { "$_" ne "$hook_sub"} @{$self->{$which}->{$method}}]
  1         3  
284             if defined $self->{$which}->{$method};
285             }
286              
287             =head2 pre_hook
288              
289             Shortcut to add a hook to a method's pre queue. Needs a method name
290             and a reference to a subroutine to be called as the hook.
291              
292             =cut
293              
294             sub pre_hook {
295 4     4 1 30 my $self = shift;
296 4         11 $self->_insert_hook(PreHooks=>@_);
297             }
298              
299             =head2 post_hook
300              
301             Shortcut to add a hook to a method's post queue. Needs a method
302             name and a reference to the subroutine to be called as the hook.
303              
304             =cut
305              
306             sub post_hook {
307 0     0 1 0 my $self = shift;
308 0         0 $self->_insert_hook(PostHooks=>@_);
309             }
310              
311             =head2 last_method
312              
313             Records the last method used to call C.
314             This allows plugins to call a method again if necessary without
315             having to know what method was actually called.
316              
317             =cut
318              
319             sub last_method {
320 11     11 1 30 my($self, $method) = @_;
321 11 100       192 $self->{LastMethod} = $method if defined $method;
322 11         43 $self->{LastMethod};
323             }
324              
325             =head1 AUTOLOAD
326              
327             This subroutine implements a mix of the "decorator" pattern and
328             the "proxy" pattern. It intercepts all the calls to the underlying class,
329             and also wraps them with pre-hooks (called before the method is called)
330             and post-hooks (called after the method is called). This allows us to
331             provide all of the functionality of C in this class
332             without copying any of the code, and to alter the behavior as well
333             without altering the original class.
334              
335             Pre-hooks can cause the actual method call to the underlying class
336             to be skipped altogether by returning a true value.
337              
338             =cut
339              
340             sub AUTOLOAD {
341 11 50   11   5520 return if $AUTOLOAD =~ /DESTROY/;
342              
343             # don't shift; this might be a straight sub call!
344 11         22 my $self = $_[0];
345              
346             # figure out what was supposed to be called.
347 11         51 (my $super_sub = $AUTOLOAD) =~ s/::Pluggable//;
348 11         74 my ($class, $plain_sub) = ($AUTOLOAD =~ /\A(.*)::(.*)$/);
349              
350             # Determine if this is a class method call or a subroutine call. Getting here
351             # for either means that they haven't been defined and we don't know how to
352             # find them.
353 11         23 my $call_type;
354 11 100 33     195 if (scalar @_ == 0 or !defined $_[0] or !ref $_[0]) {
      66        
355 1 50       5 $call_type = ( $_[0] eq $class ? 'class method' : 'subroutine' );
356             }
357              
358 11 100       357 die "Can't resolve $call_type $plain_sub(). Did your plugins define it?"
359             if $call_type;
360            
361             # Record the method name so plugins can check it.
362 10         40 $self->last_method($plain_sub);
363              
364 10         21 my ($ret, @ret) = "";
365 10         19 shift @_;
366 10         17 my $skip;
367 10 50       42 if (my $pre_hook = $self->{PreHooks}->{$plain_sub}) {
368             # skip call to actual method if pre_hook returns false.
369             # pre_hook must muck with Mech object to really return anything.
370 0         0 foreach my $hook (@$pre_hook) {
371 0         0 my $result = $hook->($self, $self->mech, @_);
372 0   0     0 $skip ||= (defined $result) && ($result == -1);
      0        
373             }
374             }
375 10 50       32 unless ($skip) {
376 10 50       29 if (wantarray) {
377 0         0 @ret = eval { $self->mech->$plain_sub(@_) };
  0         0  
378 0 0       0 croak $@ if $@;
379             }
380             else {
381 10         18 $ret = eval { $self->mech->$plain_sub(@_) };
  10         30  
382 10 100       4153 croak $@ if $@;
383             }
384             }
385 7 50       32 if (my $post_hook = $self->{PostHooks}->{$plain_sub}) {
386             # Same deal here. Anything you want to return has to go in the object.
387 0         0 foreach my $hook (@$post_hook) {
388 0         0 $hook->($self, $self->mech, @_);
389             }
390             }
391 7 50       38 wantarray ? @ret : $ret;
392             }
393              
394             =head2 clone
395              
396             An ovveride for C's C method; uses YAML to make sure
397             that the code references get cloned too. Note that this is important for
398             later code (the cache stuff in particular); general users won't notice
399             any real difference.
400              
401             There's been some discussion as to whether this is totally adequate (for
402             instance, if the code references are closures, they won't be properly cloned).
403             For now, we'll go with this and see how it works.
404              
405             =cut
406              
407             sub clone {
408 1     1 1 758 my $self = shift;
409             # Name created by eval; works out to a no-op.
410             my $value =
411 11     11   96 eval { no strict;
  11         23  
  11         1223  
  1         3  
412 1         2 local $WWW_Mechanize_Pluggable1;
413 1     1   7 eval Dump($self)->Out();
  1     1   59855  
  1     1   2  
  1     1   110  
  1     1   6  
  1     1   3  
  1     1   85  
  1         6  
  1         2  
  1         328  
  1         7  
  1         2  
  1         35  
  1         5  
  1         3  
  1         105  
  1         5  
  1         8  
  1         27  
  1         5  
  1         2  
  1         92  
414 1         8 $WWW_Mechanize_Pluggable1;
415             };
416 1 50       79 die "clone failed: $@\n" if $@;
417 1         4 return $value;
418             }
419              
420             =head1 TODO
421              
422             The plugin mechanism is ridiculously programmer-intensive. This needs to be
423             replaced with something better.
424              
425             =cut
426              
427             1; #this line is important and will help the module return a true value
428             __END__