File Coverage

blib/lib/Module/Pluggable/Object.pm
Criterion Covered Total %
statement 223 238 93.7
branch 101 130 77.6
condition 60 77 77.9
subroutine 23 25 92.0
pod 0 8 0.0
total 407 478 85.1


line stmt bran cond sub pod time code
1             package Module::Pluggable::Object;
2              
3 40     40   183767 use strict;
  40         87  
  40         1025  
4 40     40   143 use File::Find ();
  40         54  
  40         559  
5 40     40   163 use File::Basename;
  40         59  
  40         2599  
6 40     40   10864 use File::Spec::Functions qw(splitdir catdir curdir catfile abs2rel);
  40         18936  
  40         2963  
7 40     40   234 use Carp qw(croak carp confess);
  40         59  
  40         1907  
8 40     40   14232 use Devel::InnerPackage;
  40         101  
  40         1948  
9 40     40   215 use Scalar::Util qw( blessed );
  40         67  
  40         1897  
10              
11 40     40   218 use if $] > 5.017, 'deprecate';
  40         66  
  40         625  
12              
13             our $VERSION = '5.2';
14              
15             BEGIN {
16 40     40   7413 eval { require Module::Runtime };
  40         16378  
17 40 50       57687 unless ($@) {
18 40         243 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 52     52 0 76 my $class = shift;
32 52         170 my %opts = @_;
33              
34 52         158 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 73     73 0 138 my $self = shift;
45 73         163 my @args = @_;
46              
47             # override 'require'
48 73 100       353 $self->{'require'} = 1 if $self->{'inner'};
49              
50 73         162 my $filename = $self->{'filename'};
51 73         157 my $pkg = $self->{'package'};
52              
53             # Get the exception params instantiated
54 73         309 $self->_setup_exceptions;
55              
56             # automatically turn a scalar search path or namespace into a arrayref
57 73         146 for (qw(search_path search_dirs)) {
58 146 100 100     577 $self->{$_} = [ $self->{$_} ] if exists $self->{$_} && !ref($self->{$_});
59             }
60              
61             # default search path is '<Module>::<Name>::Plugin'
62 73   100     307 $self->{'search_path'} ||= ["${pkg}::Plugin"];
63              
64             # default error handler
65 73   66 0   457 $self->{'on_require_error'} ||= sub { my ($plugin, $err) = @_; carp "Couldn't require $plugin : $err"; return 0 };
  0         0  
  0         0  
  0         0  
66 73   66 0   366 $self->{'on_instantiate_error'} ||= sub { my ($plugin, $err) = @_; carp "Couldn't instantiate $plugin: $err"; return 0 };
  0         0  
  0         0  
  0         0  
67              
68             # before and after instantiation hooks
69 73   66 1   368 $self->{'before_instantiate'} ||= sub { 1 };
  1         5  
70 73   66 1   400 $self->{'after_instantiate'} ||= sub { return $_[1] };
  1         4  
71              
72             # default whether to follow symlinks
73             # because the default behavior is changed in the Perl-CORE module File::Find VERSION >= '1.39',
74             # in lower versions of File::Find, 'follow_symlinks' is (independent from the callers setting of
75             # 'follow_symlinks') hardcoded set to 0 on Windows so we force File::Find to fall back to the old
76             # behavior, if not otherwise told
77 73 50 33     726 $self->{'follow_symlinks'} = 0 if ($File::Find::VERSION >= '1.39' && $^O eq 'MSWin32' && ! exists $self->{'follow_symlinks'} );
      33        
78 73 100       254 $self->{'follow_symlinks'} = 1 unless exists $self->{'follow_symlinks'};
79              
80             # check to see if we're running under test
81 73 50 33     864 my @SEARCHDIR = exists $INC{"blib.pm"} && defined $filename && $filename =~ m!(^|/)blib/! && !$self->{'force_search_all_paths'} ? grep {/blib/} @INC : @INC;
  0         0  
82              
83             # add any search_dir params
84 73 100       222 unshift @SEARCHDIR, @{$self->{'search_dirs'}} if defined $self->{'search_dirs'};
  4         25  
85              
86             # set our @INC up to include and prefer our search_dirs if necessary
87 73         309 my @tmp = @INC;
88 73 100       122 unshift @tmp, @{$self->{'search_dirs'} || []};
  73         316  
89 73 100       191 local @INC = @tmp if defined $self->{'search_dirs'};
90              
91 73         279 my @plugins = $self->search_directories(@SEARCHDIR);
92 73         107 push(@plugins, $self->handle_inc_hooks($_, @SEARCHDIR)) for @{$self->{'search_path'}};
  73         267  
93 73         133 push(@plugins, $self->handle_innerpackages($_)) for @{$self->{'search_path'}};
  73         196  
94              
95             # return blank unless we've found anything
96 73 100       223 return () unless @plugins;
97              
98             # remove duplicates
99             # probably not necessary but hey ho
100 70         123 my %plugins;
101 70         149 for(@plugins) {
102 263 50       433 next unless $self->_is_legit($_);
103 263         502 $plugins{$_} = 1;
104             }
105              
106             # are we instantiating or requiring?
107 70 100       218 if (defined $self->{'instantiate'}) {
108 3         7 my $method = $self->{'instantiate'};
109 3         7 my @objs = ();
110 3         21 foreach my $package (sort keys %plugins) {
111 16 100       120 next unless $package->can($method);
112 2 50       30 $self->{'before_instantiate'}->($package)
113             or next;
114 2         8 my $obj = eval { $package->$method(@_) }; # We dont actually care what ->$method() returns
  2         13  
115 2 50       18 $self->{'on_instantiate_error'}->($package, $@) if $@;
116 2 50       10 if ($obj) {
117 2 50       6 $obj = $self->{'after_instantiate'}->($package,$obj)
118             or next; # Again, we dont actually care if we get a blessed reference or not
119 2         11 push @objs, $obj;
120             }
121             }
122 3         41 return @objs;
123             } else {
124             # no? just return the names
125 67         292 my @objs = sort keys %plugins;
126 67         676 return @objs;
127             }
128             }
129              
130             sub _setup_exceptions {
131 73     73   100 my $self = shift;
132              
133 73         253 my %only;
134             my %except;
135 73         0 my $only;
136 73         0 my $except;
137              
138 73 100       194 if (defined $self->{'only'}) {
139 13 100       48 if (ref($self->{'only'}) eq 'ARRAY') {
    100          
    50          
140 4         4 %only = map { $_ => 1 } @{$self->{'only'}};
  4         13  
  4         7  
141             } elsif (ref($self->{'only'}) eq 'Regexp') {
142 5         8 $only = $self->{'only'}
143             } elsif (ref($self->{'only'}) eq '') {
144 4         9 $only{$self->{'only'}} = 1;
145             }
146             }
147              
148              
149 73 100       190 if (defined $self->{'except'}) {
150 12 100       51 if (ref($self->{'except'}) eq 'ARRAY') {
    100          
    50          
151 4         6 %except = map { $_ => 1 } @{$self->{'except'}};
  4         19  
  4         13  
152             } elsif (ref($self->{'except'}) eq 'Regexp') {
153 4         6 $except = $self->{'except'}
154             } elsif (ref($self->{'except'}) eq '') {
155 4         13 $except{$self->{'except'}} = 1;
156             }
157             }
158 73         229 $self->{_exceptions}->{only_hash} = \%only;
159 73         140 $self->{_exceptions}->{only} = $only;
160 73         143 $self->{_exceptions}->{except_hash} = \%except;
161 73         179 $self->{_exceptions}->{except} = $except;
162              
163             }
164              
165             sub _is_legit {
166 573     573   601 my $self = shift;
167 573         630 my $plugin = shift;
168 573 50       599 my %only = %{$self->{_exceptions}->{only_hash}||{}};
  573         1229  
169 573 50       639 my %except = %{$self->{_exceptions}->{except_hash}||{}};
  573         1007  
170 573         746 my $only = $self->{_exceptions}->{only};
171 573         666 my $except = $self->{_exceptions}->{except};
172 573         1087 my $depth = () = split '::', $plugin, -1;
173              
174 573 100 100     1211 return 0 if (keys %only && !$only{$plugin} );
175 557 100 100     1111 return 0 unless (!defined $only || $plugin =~ m!$only! );
176              
177 548 100 100     958 return 0 if (keys %except && $except{$plugin} );
178 540 100 100     1055 return 0 if (defined $except && $plugin =~ m!$except! );
179              
180 536 100 100     921 return 0 if defined $self->{max_depth} && $depth>$self->{max_depth};
181 535 100 100     1487 return 0 if defined $self->{min_depth} && $depth<$self->{min_depth};
182              
183 532 100       886 return 0 if $plugin =~ /(^|::).AppleDouble/;
184              
185 531         1118 return 1;
186             }
187              
188             sub search_directories {
189 73     73 0 112 my $self = shift;
190 73         216 my @SEARCHDIR = @_;
191              
192 73         127 my @plugins;
193             # go through our @INC
194 73         129 foreach my $dir (@SEARCHDIR) {
195 577         962 push @plugins, $self->search_paths($dir);
196             }
197 73         263 return @plugins;
198             }
199              
200              
201             sub search_paths {
202 577     577 0 699 my $self = shift;
203 577         1047 my $dir = shift;
204 577         712 my @plugins;
205              
206 577   66     1885 my $file_regex = $self->{'file_regex'} || qr/\.pm$/;
207              
208              
209             # and each directory in our search path
210 577         729 foreach my $searchpath (@{$self->{'search_path'}}) {
  577         868  
211             # create the search directory in a cross platform goodness way
212 585         8574 my $sp = catdir($dir, (split /::/, $searchpath));
213              
214             # if it doesn't exist or it's not a dir then skip it
215 585 100 66     10718 next unless ( -e $sp && -d _ ); # Use the cached stat the second time
216              
217 80         321 my @files = $self->find_files($sp);
218              
219             # foreach one we've found
220 80         196 foreach my $file (@files) {
221             # untaint the file; accept .pm only
222 188 50       2020 next unless ($file) = ($file =~ /(.*$file_regex)$/);
223             # parse the file to get the name
224 188         4685 my ($name, $directory, $suffix) = fileparse($file, $file_regex);
225              
226 188 100 100     800 next if (!$self->{include_editor_junk} && $self->_is_editor_junk($name));
227              
228 187         524 $directory = abs2rel($directory, $sp);
229              
230             # If we have a mixed-case package name, assume case has been preserved
231             # correctly. Otherwise, root through the file to locate the case-preserved
232             # version of the package name.
233 187         10234 my @pkg_dirs = ();
234 187 100 66     834 if ( $name eq lc($name) || $name eq uc($name) ) {
235 2         10 my $pkg_file = catfile($sp, $directory, "$name$suffix");
236 2 50       67 open PKGFILE, "<$pkg_file" or die "search_paths: Can't open $pkg_file: $!";
237 2         4 my $in_pod = 0;
238 2         49 while ( my $line = <PKGFILE> ) {
239 2 50       6 $in_pod = 1 if $line =~ m/^=\w/;
240 2 50       5 $in_pod = 0 if $line =~ /^=cut/;
241 2 50 33     6 next if ($in_pod || $line =~ /^=cut/); # skip pod text
242 2 50       9 next if $line =~ /^\s*#/; # and comments
243 2 50       71 if ( $line =~ m/^\s*package\s+(.*::)?($name)\s*;/i ) {
244 2 50       27 @pkg_dirs = split /::/, $1 if defined $1;;
245 2         4 $name = $2;
246 2         4 last;
247             }
248             }
249 2         24 close PKGFILE;
250             }
251              
252             # then create the class name in a cross platform way
253 187 50       766 $directory =~ s/^[a-z]://i if($^O =~ /MSWin32|dos/); # remove volume
254 187         261 my @dirs = ();
255 187 50       386 if ($directory) {
256 187         543 ($directory) = ($directory =~ /(.*)/);
257 187 100       472 @dirs = grep(length($_), splitdir($directory))
258             unless $directory eq curdir();
259 187         579 for my $d (reverse @dirs) {
260 47         69 my $pkg_dir = pop @pkg_dirs;
261 47 50       124 last unless defined $pkg_dir;
262 0         0 $d =~ s/\Q$pkg_dir\E/$pkg_dir/i; # Correct case
263             }
264             } else {
265 0         0 $directory = "";
266             }
267 187         503 my $plugin = join '::', $searchpath, @dirs, $name;
268              
269 187 50       718 next unless $plugin =~ m!(?:[a-z\d]+)[a-z\d]*!i;
270              
271 187         601 $self->handle_finding_plugin($plugin, \@plugins)
272             }
273              
274             # now add stuff that may have been in package
275             # NOTE we should probably use all the stuff we've been given already
276             # but then we can't unload it :(
277 80         270 push @plugins, $self->handle_innerpackages($searchpath);
278             } # foreach $searchpath
279              
280 577         1342 return @plugins;
281             }
282              
283             sub _is_editor_junk {
284 185     185   254 my $self = shift;
285 185         256 my $name = shift;
286              
287             # Emacs (and other Unix-y editors) leave temp files ending in a
288             # tilde as a backup.
289 185 50       443 return 1 if $name =~ /~$/;
290             # Emacs makes these files while a buffer is edited but not yet
291             # saved.
292 185 100       369 return 1 if $name =~ /^\.#/;
293             # Vim can leave these files behind if it crashes.
294 184 50       375 return 1 if $name =~ /^[._].*\.s[a-w][a-z]$/;
295              
296 184         555 return 0;
297             }
298              
299             sub handle_finding_plugin {
300 310     310 0 399 my $self = shift;
301 310         426 my $plugin = shift;
302 310         366 my $plugins = shift;
303 310   100     712 my $no_req = shift || 0;
304              
305 310 100       622 return unless $self->_is_legit($plugin);
306 268 100 100     774 unless (defined $self->{'instantiate'} || $self->{'require'}) {
307 125         226 push @$plugins, $plugin;
308 125         301 return;
309             }
310              
311 143 100 100     226 $self->{before_require}->($plugin) || return if defined $self->{before_require};
312 142 100       233 unless ($no_req) {
313 32         70 my $tmp = $@;
314 32         49 my $res = eval { require_module($plugin) };
  32         86  
315 32         79999 my $err = $@;
316 32         68 $@ = $tmp;
317 32 100       90 if ($err) {
318 1 50       4 if (defined $self->{on_require_error}) {
319 1 50       4 $self->{on_require_error}->($plugin, $err) || return;
320             } else {
321 0         0 return;
322             }
323             }
324             }
325 141 100 100     235 $self->{after_require}->($plugin) || return if defined $self->{after_require};
326 138         327 push @$plugins, $plugin;
327             }
328              
329             sub find_files {
330 81     81 0 126 my $self = shift;
331 81         120 my $search_path = shift;
332 81   66     474 my $file_regex = $self->{'file_regex'} || qr/\.pm$/;
333              
334              
335             # find all the .pm files in it
336             # this isn't perfect and won't find multiple plugins per file
337             #my $cwd = Cwd::getcwd;
338 81         134 my @files = ();
339             { # for the benefit of perl 5.6.1's Find, localize topic
340 81         138 local $_;
  81         139  
341             File::Find::find( { no_chdir => 1,
342             follow => $self->{'follow_symlinks'},
343             wanted => sub {
344             # Inlined from File::Find::Rule C< name => '*.pm' >
345 325 100   325   15757 return unless $File::Find::name =~ /$file_regex/;
346 191         389 (my $path = $File::Find::name) =~ s#^\\./##;
347 191         4046 push @files, $path;
348             }
349 81         9520 }, $search_path );
350             }
351             #chdir $cwd;
352 81         589 return @files;
353              
354             }
355              
356             sub handle_inc_hooks {
357 74     74 0 116 my $self = shift;
358 74         112 my $path = shift;
359 74         272 my @SEARCHDIR = @_;
360              
361 74         123 my @plugins;
362 74         159 for my $dir ( @SEARCHDIR ) {
363 585 100 66     1061 next unless blessed( $dir ) && $dir->can( 'files' );
364              
365 1         4 foreach my $plugin ( $dir->files ) {
366 1         9 $plugin =~ s/\.pm$//;
367 1         3 $plugin =~ s{/}{::}g;
368 1 50       20 next unless $plugin =~ m!^${path}::!;
369 1         5 $self->handle_finding_plugin( $plugin, \@plugins );
370             }
371             }
372 74         173 return @plugins;
373             }
374              
375             sub handle_innerpackages {
376 154     154 0 185 my $self = shift;
377 154 100 100     389 return () if (exists $self->{inner} && !$self->{inner});
378              
379 151         199 my $path = shift;
380 151         183 my @plugins;
381              
382 151         454 foreach my $plugin (Devel::InnerPackage::list_packages($path)) {
383 122         237 $self->handle_finding_plugin($plugin, \@plugins, 1);
384             }
385 151         375 return @plugins;
386              
387             }
388              
389             1;
390              
391             =pod
392              
393             =head1 NAME
394              
395             Module::Pluggable::Object - automatically give your module the ability to have plugins
396              
397             =head1 SYNOPSIS
398              
399              
400             Simple use Module::Pluggable -
401              
402             package MyClass;
403             use Module::Pluggable::Object;
404              
405             my $finder = Module::Pluggable::Object->new(%opts);
406             print "My plugins are: ".join(", ", $finder->plugins)."\n";
407              
408             =head1 DESCRIPTION
409              
410             Provides a simple but, hopefully, extensible way of having 'plugins' for
411             your module. Obviously this isn't going to be the be all and end all of
412             solutions but it works for me.
413              
414             Essentially all it does is export a method into your namespace that
415             looks through a search path for .pm files and turn those into class names.
416              
417             Optionally it instantiates those classes for you.
418              
419             This object is wrapped by C<Module::Pluggable>. If you want to do something
420             odd or add non-general special features you're probably best to wrap this
421             and produce your own subclass.
422              
423             =head1 OPTIONS
424              
425             See the C<Module::Pluggable> docs.
426              
427             =head1 AUTHOR
428              
429             Simon Wistow <simon@thegestalt.org>
430              
431             =head1 COPYING
432              
433             Copyright, 2006 Simon Wistow
434              
435             Distributed under the same terms as Perl itself.
436              
437             =head1 BUGS
438              
439             None known.
440              
441             =head1 SEE ALSO
442              
443             L<Module::Pluggable>
444              
445             =cut
446