File Coverage

blib/lib/Module/Pluggable/Object.pm
Criterion Covered Total %
statement 213 230 92.6
branch 96 122 78.6
condition 52 63 82.5
subroutine 21 23 91.3
pod 0 8 0.0
total 382 446 85.6


line stmt bran cond sub pod time code
1             package Module::Pluggable::Object;
2              
3 39     39   47363 use strict;
  39         53  
  39         1082  
4 39     39   169 use File::Find ();
  39         48  
  39         599  
5 39     39   148 use File::Basename;
  39         52  
  39         3024  
6 39     39   16284 use File::Spec::Functions qw(splitdir catdir curdir catfile abs2rel);
  39         21339  
  39         3018  
7 39     39   212 use Carp qw(croak carp confess);
  39         54  
  39         2190  
8 39     39   16763 use Devel::InnerPackage;
  39         82  
  39         1951  
9 39     39   194 use vars qw($VERSION $MR);
  39         51  
  39         2221  
10              
11 39     39   177 use if $] > 5.017, 'deprecate';
  39         51  
  39         245  
12              
13             $VERSION = '5.2';
14              
15             BEGIN {
16 39     39   7803 eval { require Module::Runtime };
  39         22690  
17 39 50       59596 unless ($@) {
18 39         223 Module::Runtime->import('require_module');
19             } else {
20             *require_module = sub {
21 0         0 my $module = shift;
22 0         0 my $path = $module . ".pm";
23 0         0 $path =~ s{::}{/}g;
24 0         0 require $path;
25 0         0 };
26             }
27             }
28              
29              
30             sub new {
31 51     51 0 111 my $class = shift;
32 51         145 my %opts = @_;
33              
34 51         172 return bless \%opts, $class;
35              
36             }
37              
38             ### Eugggh, this code smells
39             ### This is what happens when you keep adding patches
40             ### *sigh*
41              
42              
43             sub plugins {
44 72     72 0 116 my $self = shift;
45 72         136 my @args = @_;
46              
47             # override 'require'
48 72 100       377 $self->{'require'} = 1 if $self->{'inner'};
49              
50 72         147 my $filename = $self->{'filename'};
51 72         148 my $pkg = $self->{'package'};
52              
53             # Get the exception params instantiated
54 72         219 $self->_setup_exceptions;
55              
56             # automatically turn a scalar search path or namespace into a arrayref
57 72         166 for (qw(search_path search_dirs)) {
58 144 100 100     659 $self->{$_} = [ $self->{$_} ] if exists $self->{$_} && !ref($self->{$_});
59             }
60              
61             # default search path is '::::Plugin'
62 72   100     341 $self->{'search_path'} ||= ["${pkg}::Plugin"];
63              
64             # default error handler
65 72   100 0   461 $self->{'on_require_error'} ||= sub { my ($plugin, $err) = @_; carp "Couldn't require $plugin : $err"; return 0 };
  0         0  
  0         0  
  0         0  
66 72   100 0   424 $self->{'on_instantiate_error'} ||= sub { my ($plugin, $err) = @_; carp "Couldn't instantiate $plugin: $err"; return 0 };
  0         0  
  0         0  
  0         0  
67              
68             # default whether to follow symlinks
69 72 100       249 $self->{'follow_symlinks'} = 1 unless exists $self->{'follow_symlinks'};
70              
71             # check to see if we're running under test
72 72 50 33     1030 my @SEARCHDIR = exists $INC{"blib.pm"} && defined $filename && $filename =~ m!(^|/)blib/! && !$self->{'force_search_all_paths'} ? grep {/blib/} @INC : @INC;
  0         0  
73              
74             # add any search_dir params
75 72 100       237 unshift @SEARCHDIR, @{$self->{'search_dirs'}} if defined $self->{'search_dirs'};
  4         14  
76              
77             # set our @INC up to include and prefer our search_dirs if necessary
78 72         236 my @tmp = @INC;
79 72 100       103 unshift @tmp, @{$self->{'search_dirs'} || []};
  72         329  
80 72 100       220 local @INC = @tmp if defined $self->{'search_dirs'};
81              
82 72         253 my @plugins = $self->search_directories(@SEARCHDIR);
83 72         106 push(@plugins, $self->handle_inc_hooks($_, @SEARCHDIR)) for @{$self->{'search_path'}};
  72         357  
84 72         120 push(@plugins, $self->handle_innerpackages($_)) for @{$self->{'search_path'}};
  72         266  
85            
86             # return blank unless we've found anything
87 72 100       205 return () unless @plugins;
88              
89             # remove duplicates
90             # probably not necessary but hey ho
91 69         88 my %plugins;
92 69         127 for(@plugins) {
93 212 50       337 next unless $self->_is_legit($_);
94 212         383 $plugins{$_} = 1;
95             }
96              
97             # are we instantiating or requiring?
98 69 100       293 if (defined $self->{'instantiate'}) {
99 3         4 my $method = $self->{'instantiate'};
100 3         4 my @objs = ();
101 3         15 foreach my $package (sort keys %plugins) {
102 9 100       78 next unless $package->can($method);
103 2         4 my $obj = eval { $package->$method(@_) };
  2         10  
104 2 50       14 $self->{'on_instantiate_error'}->($package, $@) if $@;
105 2 50       13 push @objs, $obj if $obj;
106             }
107 3         28 return @objs;
108             } else {
109             # no? just return the names
110 66         322 my @objs= sort keys %plugins;
111 66         589 return @objs;
112             }
113             }
114              
115             sub _setup_exceptions {
116 72     72   98 my $self = shift;
117              
118 72         97 my %only;
119             my %except;
120 0         0 my $only;
121 0         0 my $except;
122              
123 72 100       203 if (defined $self->{'only'}) {
124 13 100       51 if (ref($self->{'only'}) eq 'ARRAY') {
    100          
    50          
125 4         5 %only = map { $_ => 1 } @{$self->{'only'}};
  4         16  
  4         10  
126             } elsif (ref($self->{'only'}) eq 'Regexp') {
127 5         11 $only = $self->{'only'}
128             } elsif (ref($self->{'only'}) eq '') {
129 4         8 $only{$self->{'only'}} = 1;
130             }
131             }
132            
133              
134 72 100       187 if (defined $self->{'except'}) {
135 12 100       51 if (ref($self->{'except'}) eq 'ARRAY') {
    100          
    50          
136 4         5 %except = map { $_ => 1 } @{$self->{'except'}};
  4         15  
  4         12  
137             } elsif (ref($self->{'except'}) eq 'Regexp') {
138 4         5 $except = $self->{'except'}
139             } elsif (ref($self->{'except'}) eq '') {
140 4         11 $except{$self->{'except'}} = 1;
141             }
142             }
143 72         220 $self->{_exceptions}->{only_hash} = \%only;
144 72         182 $self->{_exceptions}->{only} = $only;
145 72         158 $self->{_exceptions}->{except_hash} = \%except;
146 72         162 $self->{_exceptions}->{except} = $except;
147            
148             }
149              
150             sub _is_legit {
151 470     470   463 my $self = shift;
152 470         423 my $plugin = shift;
153 470 50       374 my %only = %{$self->{_exceptions}->{only_hash}||{}};
  470         1526  
154 470 50       446 my %except = %{$self->{_exceptions}->{except_hash}||{}};
  470         1151  
155 470         563 my $only = $self->{_exceptions}->{only};
156 470         444 my $except = $self->{_exceptions}->{except};
157 470         1107 my $depth = () = split '::', $plugin, -1;
158              
159 470 100 100     1344 return 0 if (keys %only && !$only{$plugin} );
160 454 100 100     1511 return 0 unless (!defined $only || $plugin =~ m!$only! );
161              
162 445 100 66     804 return 0 if (keys %except && $except{$plugin} );
163 437 100 100     879 return 0 if (defined $except && $plugin =~ m!$except! );
164            
165 433 100 100     851 return 0 if defined $self->{max_depth} && $depth>$self->{max_depth};
166 432 100 100     803 return 0 if defined $self->{min_depth} && $depth<$self->{min_depth};
167              
168 429         1072 return 1;
169             }
170              
171             sub search_directories {
172 72     72 0 104 my $self = shift;
173 72         204 my @SEARCHDIR = @_;
174              
175 72         97 my @plugins;
176             # go through our @INC
177 72         125 foreach my $dir (@SEARCHDIR) {
178 720         1120 push @plugins, $self->search_paths($dir);
179             }
180 72         312 return @plugins;
181             }
182              
183              
184             sub search_paths {
185 720     720 0 651 my $self = shift;
186 720         686 my $dir = shift;
187 720         494 my @plugins;
188              
189 720   66     3015 my $file_regex = $self->{'file_regex'} || qr/\.pm$/;
190              
191              
192             # and each directory in our search path
193 720         605 foreach my $searchpath (@{$self->{'search_path'}}) {
  720         1203  
194             # create the search directory in a cross platform goodness way
195 730         2598 my $sp = catdir($dir, (split /::/, $searchpath));
196              
197             # if it doesn't exist or it's not a dir then skip it
198 730 100 66     12269 next unless ( -e $sp && -d _ ); # Use the cached stat the second time
199              
200 77         263 my @files = $self->find_files($sp);
201              
202             # foreach one we've found
203 77         201 foreach my $file (@files) {
204             # untaint the file; accept .pm only
205 180 50       1735 next unless ($file) = ($file =~ /(.*$file_regex)$/);
206             # parse the file to get the name
207 180         4811 my ($name, $directory, $suffix) = fileparse($file, $file_regex);
208              
209 180 100 100     848 next if (!$self->{include_editor_junk} && $self->_is_editor_junk($name));
210              
211 179         551 $directory = abs2rel($directory, $sp);
212              
213             # If we have a mixed-case package name, assume case has been preserved
214             # correctly. Otherwise, root through the file to locate the case-preserved
215             # version of the package name.
216 179         12251 my @pkg_dirs = ();
217 179 100 66     928 if ( $name eq lc($name) || $name eq uc($name) ) {
218 2         16 my $pkg_file = catfile($sp, $directory, "$name$suffix");
219 2 50       86 open PKGFILE, "<$pkg_file" or die "search_paths: Can't open $pkg_file: $!";
220 2         6 my $in_pod = 0;
221 2         29 while ( my $line = ) {
222 2 50       11 $in_pod = 1 if $line =~ m/^=\w/;
223 2 50       9 $in_pod = 0 if $line =~ /^=cut/;
224 2 50 33     17 next if ($in_pod || $line =~ /^=cut/); # skip pod text
225 2 50       8 next if $line =~ /^\s*#/; # and comments
226 2 50       53 if ( $line =~ m/^\s*package\s+(.*::)?($name)\s*;/i ) {
227 2 50       14 @pkg_dirs = split /::/, $1 if defined $1;;
228 2         9 $name = $2;
229 2         6 last;
230             }
231             }
232 2         22 close PKGFILE;
233             }
234              
235             # then create the class name in a cross platform way
236 179 50       800 $directory =~ s/^[a-z]://i if($^O =~ /MSWin32|dos/); # remove volume
237 179         236 my @dirs = ();
238 179 50       330 if ($directory) {
239 179         522 ($directory) = ($directory =~ /(.*)/);
240 179 100       454 @dirs = grep(length($_), splitdir($directory))
241             unless $directory eq curdir();
242 179         479 for my $d (reverse @dirs) {
243 42         57 my $pkg_dir = pop @pkg_dirs;
244 42 50       116 last unless defined $pkg_dir;
245 0         0 $d =~ s/\Q$pkg_dir\E/$pkg_dir/i; # Correct case
246             }
247             } else {
248 0         0 $directory = "";
249             }
250 179         442 my $plugin = join '::', $searchpath, @dirs, $name;
251              
252 179 50       605 next unless $plugin =~ m!(?:[a-z\d]+)[a-z\d]*!i;
253              
254 179         461 $self->handle_finding_plugin($plugin, \@plugins)
255             }
256              
257             # now add stuff that may have been in package
258             # NOTE we should probably use all the stuff we've been given already
259             # but then we can't unload it :(
260 77         300 push @plugins, $self->handle_innerpackages($searchpath);
261             } # foreach $searchpath
262              
263 720         1562 return @plugins;
264             }
265              
266             sub _is_editor_junk {
267 177     177   212 my $self = shift;
268 177         183 my $name = shift;
269              
270             # Emacs (and other Unix-y editors) leave temp files ending in a
271             # tilde as a backup.
272 177 50       404 return 1 if $name =~ /~$/;
273             # Emacs makes these files while a buffer is edited but not yet
274             # saved.
275 177 100       362 return 1 if $name =~ /^\.#/;
276             # Vim can leave these files behind if it crashes.
277 176 50       307 return 1 if $name =~ /\.sw[po]$/;
278              
279 176         590 return 0;
280             }
281              
282             sub handle_finding_plugin {
283 258     258 0 274 my $self = shift;
284 258         245 my $plugin = shift;
285 258         268 my $plugins = shift;
286 258   100     662 my $no_req = shift || 0;
287            
288 258 100       515 return unless $self->_is_legit($plugin);
289 217 100 66     801 unless (defined $self->{'instantiate'} || $self->{'require'}) {
290 124         183 push @$plugins, $plugin;
291 124         289 return;
292             }
293              
294 93 100 100     182 $self->{before_require}->($plugin) || return if defined $self->{before_require};
295 92 100       189 unless ($no_req) {
296 26         37 my $tmp = $@;
297 26         34 my $res = eval { require_module($plugin) };
  26         85  
298 26         49397 my $err = $@;
299 26         44 $@ = $tmp;
300 26 100       90 if ($err) {
301 1 50       4 if (defined $self->{on_require_error}) {
302 1 50       5 $self->{on_require_error}->($plugin, $err) || return;
303             } else {
304 0         0 return;
305             }
306             }
307             }
308 91 100 100     184 $self->{after_require}->($plugin) || return if defined $self->{after_require};
309 88         278 push @$plugins, $plugin;
310             }
311              
312             sub find_files {
313 78     78 0 106 my $self = shift;
314 78         103 my $search_path = shift;
315 78   66     451 my $file_regex = $self->{'file_regex'} || qr/\.pm$/;
316              
317              
318             # find all the .pm files in it
319             # this isn't perfect and won't find multiple plugins per file
320             #my $cwd = Cwd::getcwd;
321 78         140 my @files = ();
322             { # for the benefit of perl 5.6.1's Find, localize topic
323 78         87 local $_;
  78         95  
324             File::Find::find( { no_chdir => 1,
325             follow => $self->{'follow_symlinks'},
326             wanted => sub {
327             # Inlined from File::Find::Rule C< name => '*.pm' >
328 311 100   311   11252 return unless $File::Find::name =~ /$file_regex/;
329 183         322 (my $path = $File::Find::name) =~ s#^\\./##;
330 183         5050 push @files, $path;
331             }
332 78         9480 }, $search_path );
333             }
334             #chdir $cwd;
335 78         570 return @files;
336              
337             }
338              
339             sub handle_inc_hooks {
340 73     73 0 102 my $self = shift;
341 73         100 my $path = shift;
342 73         195 my @SEARCHDIR = @_;
343              
344 73         83 my @plugins;
345 73         127 for my $dir ( @SEARCHDIR ) {
346 730 100 66     1364 next unless ref $dir && eval { $dir->can( 'files' ) };
  1         12  
347              
348 1         6 foreach my $plugin ( $dir->files ) {
349 1         11 $plugin =~ s/\.pm$//;
350 1         4 $plugin =~ s{/}{::}g;
351 1 50       21 next unless $plugin =~ m!^${path}::!;
352 1         7 $self->handle_finding_plugin( $plugin, \@plugins );
353             }
354             }
355 73         195 return @plugins;
356             }
357              
358             sub handle_innerpackages {
359 150     150 0 176 my $self = shift;
360 150 100 100     416 return () if (exists $self->{inner} && !$self->{inner});
361              
362 147         168 my $path = shift;
363 147         141 my @plugins;
364              
365 147         460 foreach my $plugin (Devel::InnerPackage::list_packages($path)) {
366 78         184 $self->handle_finding_plugin($plugin, \@plugins, 1);
367             }
368 147         334 return @plugins;
369              
370             }
371              
372             1;
373              
374             =pod
375              
376             =head1 NAME
377              
378             Module::Pluggable::Object - automatically give your module the ability to have plugins
379              
380             =head1 SYNOPSIS
381              
382              
383             Simple use Module::Pluggable -
384              
385             package MyClass;
386             use Module::Pluggable::Object;
387            
388             my $finder = Module::Pluggable::Object->new(%opts);
389             print "My plugins are: ".join(", ", $finder->plugins)."\n";
390              
391             =head1 DESCRIPTION
392              
393             Provides a simple but, hopefully, extensible way of having 'plugins' for
394             your module. Obviously this isn't going to be the be all and end all of
395             solutions but it works for me.
396              
397             Essentially all it does is export a method into your namespace that
398             looks through a search path for .pm files and turn those into class names.
399              
400             Optionally it instantiates those classes for you.
401              
402             This object is wrapped by C. If you want to do something
403             odd or add non-general special features you're probably best to wrap this
404             and produce your own subclass.
405              
406             =head1 OPTIONS
407              
408             See the C docs.
409              
410             =head1 AUTHOR
411              
412             Simon Wistow
413              
414             =head1 COPYING
415              
416             Copyright, 2006 Simon Wistow
417              
418             Distributed under the same terms as Perl itself.
419              
420             =head1 BUGS
421              
422             None known.
423              
424             =head1 SEE ALSO
425              
426             L
427              
428             =cut
429