| Filename | /Users/timbo/perl5/perlbrew/perls/perl-5.18.2/lib/site_perl/5.18.2/Module/Pluggable.pm |
| Statements | Executed 34 statements in 766µs |
| Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
|---|---|---|---|---|---|
| 1 | 1 | 1 | 1.84ms | 11.7ms | Module::Pluggable::BEGIN@5 |
| 1 | 1 | 1 | 33µs | 38µs | Module::Pluggable::import |
| 1 | 1 | 1 | 17µs | 35µs | Module::Pluggable::BEGIN@3 |
| 1 | 1 | 1 | 15µs | 113µs | Module::Pluggable::BEGIN@7 |
| 1 | 1 | 1 | 11µs | 22µs | Module::Pluggable::BEGIN@66 |
| 1 | 1 | 1 | 10µs | 55µs | Module::Pluggable::BEGIN@4 |
| 1 | 1 | 1 | 9µs | 20µs | Module::Pluggable::BEGIN@67 |
| 1 | 1 | 1 | 8µs | 1.29s | Module::Pluggable::__ANON__[:32] |
| 0 | 0 | 0 | 0s | 0s | Module::Pluggable::__ANON__[:42] |
| 0 | 0 | 0 | 0s | 0s | Module::Pluggable::__ANON__[:53] |
| 0 | 0 | 0 | 0s | 0s | Module::Pluggable::__ANON__[:63] |
| Line | State ments |
Time on line |
Calls | Time in subs |
Code |
|---|---|---|---|---|---|
| 1 | package Module::Pluggable; | ||||
| 2 | |||||
| 3 | 2 | 32µs | 2 | 52µs | # spent 35µs (17+18) within Module::Pluggable::BEGIN@3 which was called:
# once (17µs+18µs) by Perl::Critic::PolicyFactory::import at line 3 # spent 35µs making 1 call to Module::Pluggable::BEGIN@3
# spent 18µs making 1 call to strict::import |
| 4 | 2 | 34µs | 2 | 99µs | # spent 55µs (10+44) within Module::Pluggable::BEGIN@4 which was called:
# once (10µs+44µs) by Perl::Critic::PolicyFactory::import at line 4 # spent 55µs making 1 call to Module::Pluggable::BEGIN@4
# spent 44µs making 1 call to vars::import |
| 5 | 2 | 119µs | 1 | 11.7ms | # spent 11.7ms (1.84+9.83) within Module::Pluggable::BEGIN@5 which was called:
# once (1.84ms+9.83ms) by Perl::Critic::PolicyFactory::import at line 5 # spent 11.7ms making 1 call to Module::Pluggable::BEGIN@5 |
| 6 | |||||
| 7 | 2 | 341µs | 2 | 116µs | # spent 113µs (15+98) within Module::Pluggable::BEGIN@7 which was called:
# once (15µs+98µs) by Perl::Critic::PolicyFactory::import at line 7 # spent 113µs making 1 call to Module::Pluggable::BEGIN@7
# spent 3µs making 1 call to if::import |
| 8 | |||||
| 9 | # ObQuote: | ||||
| 10 | # Bob Porter: Looks like you've been missing a lot of work lately. | ||||
| 11 | # Peter Gibbons: I wouldn't say I've been missing it, Bob! | ||||
| 12 | |||||
| 13 | |||||
| 14 | 1 | 700ns | $VERSION = '5.1'; | ||
| 15 | 1 | 100ns | $FORCE_SEARCH_ALL_PATHS = 0; | ||
| 16 | |||||
| 17 | # spent 38µs (33+6) within Module::Pluggable::import which was called:
# once (33µs+6µs) by Perl::Critic::PolicyFactory::import at line 59 of Perl/Critic/PolicyFactory.pm | ||||
| 18 | 1 | 400ns | my $class = shift; | ||
| 19 | 1 | 4µs | 1 | 2µs | my %opts = @_; # spent 2µs making 1 call to Readonly::Scalar::FETCH |
| 20 | |||||
| 21 | 1 | 1µs | my ($pkg, $file) = caller; | ||
| 22 | # the default name for the method is 'plugins' | ||||
| 23 | 1 | 600ns | my $sub = $opts{'sub_name'} || 'plugins'; | ||
| 24 | # get our package | ||||
| 25 | 1 | 300ns | my ($package) = $opts{'package'} || $pkg; | ||
| 26 | 1 | 400ns | $opts{filename} = $file; | ||
| 27 | 1 | 100ns | $opts{package} = $package; | ||
| 28 | 1 | 500ns | $opts{force_search_all_paths} = $FORCE_SEARCH_ALL_PATHS unless exists $opts{force_search_all_paths}; | ||
| 29 | |||||
| 30 | |||||
| 31 | 1 | 3µs | 1 | 4µs | my $finder = Module::Pluggable::Object->new(%opts); # spent 4µs making 1 call to Module::Pluggable::Object::new |
| 32 | 3 | 8µs | 1 | 1.29s | # spent 1.29s (8µs+1.29) within Module::Pluggable::__ANON__[/Users/timbo/perl5/perlbrew/perls/perl-5.18.2/lib/site_perl/5.18.2/Module/Pluggable.pm:32] which was called:
# once (8µs+1.29s) by Perl::Critic::PolicyFactory::import at line 61 of Perl/Critic/PolicyFactory.pm # spent 1.29s making 1 call to Module::Pluggable::Object::plugins |
| 33 | |||||
| 34 | my $searchsub = sub { | ||||
| 35 | my $self = shift; | ||||
| 36 | my ($action,@paths) = @_; | ||||
| 37 | |||||
| 38 | $finder->{'search_path'} = ["${package}::Plugin"] if ($action eq 'add' and not $finder->{'search_path'} ); | ||||
| 39 | push @{$finder->{'search_path'}}, @paths if ($action eq 'add'); | ||||
| 40 | $finder->{'search_path'} = \@paths if ($action eq 'new'); | ||||
| 41 | return $finder->{'search_path'}; | ||||
| 42 | 1 | 2µs | }; | ||
| 43 | |||||
| 44 | |||||
| 45 | my $onlysub = sub { | ||||
| 46 | my ($self, $only) = @_; | ||||
| 47 | |||||
| 48 | if (defined $only) { | ||||
| 49 | $finder->{'only'} = $only; | ||||
| 50 | }; | ||||
| 51 | |||||
| 52 | return $finder->{'only'}; | ||||
| 53 | 1 | 1µs | }; | ||
| 54 | |||||
| 55 | my $exceptsub = sub { | ||||
| 56 | my ($self, $except) = @_; | ||||
| 57 | |||||
| 58 | if (defined $except) { | ||||
| 59 | $finder->{'except'} = $except; | ||||
| 60 | }; | ||||
| 61 | |||||
| 62 | return $finder->{'except'}; | ||||
| 63 | 1 | 800ns | }; | ||
| 64 | |||||
| 65 | |||||
| 66 | 2 | 23µs | 2 | 32µs | # spent 22µs (11+11) within Module::Pluggable::BEGIN@66 which was called:
# once (11µs+11µs) by Perl::Critic::PolicyFactory::import at line 66 # spent 22µs making 1 call to Module::Pluggable::BEGIN@66
# spent 10µs making 1 call to strict::unimport |
| 67 | 2 | 180µs | 2 | 32µs | # spent 20µs (9+12) within Module::Pluggable::BEGIN@67 which was called:
# once (9µs+12µs) by Perl::Critic::PolicyFactory::import at line 67 # spent 20µs making 1 call to Module::Pluggable::BEGIN@67
# spent 12µs making 1 call to warnings::unimport |
| 68 | |||||
| 69 | 1 | 2µs | *{"$package\::$sub"} = $subroutine; | ||
| 70 | 1 | 2µs | *{"$package\::search_path"} = $searchsub; | ||
| 71 | 1 | 1µs | *{"$package\::only"} = $onlysub; | ||
| 72 | 1 | 6µs | *{"$package\::except"} = $exceptsub; | ||
| 73 | |||||
| 74 | } | ||||
| 75 | |||||
| 76 | 1 | 2µs | 1; | ||
| 77 | |||||
| 78 | =pod | ||||
| 79 | |||||
| 80 | =head1 NAME | ||||
| 81 | |||||
| 82 | Module::Pluggable - automatically give your module the ability to have plugins | ||||
| 83 | |||||
| 84 | =head1 SYNOPSIS | ||||
| 85 | |||||
| 86 | |||||
| 87 | Simple use Module::Pluggable - | ||||
| 88 | |||||
| 89 | package MyClass; | ||||
| 90 | use Module::Pluggable; | ||||
| 91 | |||||
| 92 | |||||
| 93 | and then later ... | ||||
| 94 | |||||
| 95 | use MyClass; | ||||
| 96 | my $mc = MyClass->new(); | ||||
| 97 | # returns the names of all plugins installed under MyClass::Plugin::* | ||||
| 98 | my @plugins = $mc->plugins(); | ||||
| 99 | |||||
| 100 | =head1 EXAMPLE | ||||
| 101 | |||||
| 102 | Why would you want to do this? Say you have something that wants to pass an | ||||
| 103 | object to a number of different plugins in turn. For example you may | ||||
| 104 | want to extract meta-data from every email you get sent and do something | ||||
| 105 | with it. Plugins make sense here because then you can keep adding new | ||||
| 106 | meta data parsers and all the logic and docs for each one will be | ||||
| 107 | self contained and new handlers are easy to add without changing the | ||||
| 108 | core code. For that, you might do something like ... | ||||
| 109 | |||||
| 110 | package Email::Examiner; | ||||
| 111 | |||||
| 112 | use strict; | ||||
| 113 | use Email::Simple; | ||||
| 114 | use Module::Pluggable require => 1; | ||||
| 115 | |||||
| 116 | sub handle_email { | ||||
| 117 | my $self = shift; | ||||
| 118 | my $email = shift; | ||||
| 119 | |||||
| 120 | foreach my $plugin ($self->plugins) { | ||||
| 121 | $plugin->examine($email); | ||||
| 122 | } | ||||
| 123 | |||||
| 124 | return 1; | ||||
| 125 | } | ||||
| 126 | |||||
| - - | |||||
| 129 | .. and all the plugins will get a chance in turn to look at it. | ||||
| 130 | |||||
| 131 | This can be trivially extended so that plugins could save the email | ||||
| 132 | somewhere and then no other plugin should try and do that. | ||||
| 133 | Simply have it so that the C<examine> method returns C<1> if | ||||
| 134 | it has saved the email somewhere. You might also want to be paranoid | ||||
| 135 | and check to see if the plugin has an C<examine> method. | ||||
| 136 | |||||
| 137 | foreach my $plugin ($self->plugins) { | ||||
| 138 | next unless $plugin->can('examine'); | ||||
| 139 | last if $plugin->examine($email); | ||||
| 140 | } | ||||
| 141 | |||||
| 142 | |||||
| 143 | And so on. The sky's the limit. | ||||
| 144 | |||||
| 145 | |||||
| 146 | =head1 DESCRIPTION | ||||
| 147 | |||||
| 148 | Provides a simple but, hopefully, extensible way of having 'plugins' for | ||||
| 149 | your module. Obviously this isn't going to be the be all and end all of | ||||
| 150 | solutions but it works for me. | ||||
| 151 | |||||
| 152 | Essentially all it does is export a method into your namespace that | ||||
| 153 | looks through a search path for .pm files and turn those into class names. | ||||
| 154 | |||||
| 155 | Optionally it instantiates those classes for you. | ||||
| 156 | |||||
| 157 | =head1 ADVANCED USAGE | ||||
| 158 | |||||
| 159 | Alternatively, if you don't want to use 'plugins' as the method ... | ||||
| 160 | |||||
| 161 | package MyClass; | ||||
| 162 | use Module::Pluggable sub_name => 'foo'; | ||||
| 163 | |||||
| 164 | |||||
| 165 | and then later ... | ||||
| 166 | |||||
| 167 | my @plugins = $mc->foo(); | ||||
| 168 | |||||
| 169 | |||||
| 170 | Or if you want to look in another namespace | ||||
| 171 | |||||
| 172 | package MyClass; | ||||
| 173 | use Module::Pluggable search_path => ['Acme::MyClass::Plugin', 'MyClass::Extend']; | ||||
| 174 | |||||
| 175 | or directory | ||||
| 176 | |||||
| 177 | use Module::Pluggable search_dirs => ['mylibs/Foo']; | ||||
| 178 | |||||
| 179 | |||||
| 180 | Or if you want to instantiate each plugin rather than just return the name | ||||
| 181 | |||||
| 182 | package MyClass; | ||||
| 183 | use Module::Pluggable instantiate => 'new'; | ||||
| 184 | |||||
| 185 | and then | ||||
| 186 | |||||
| 187 | # whatever is passed to 'plugins' will be passed | ||||
| 188 | # to 'new' for each plugin | ||||
| 189 | my @plugins = $mc->plugins(@options); | ||||
| 190 | |||||
| 191 | |||||
| 192 | alternatively you can just require the module without instantiating it | ||||
| 193 | |||||
| 194 | package MyClass; | ||||
| 195 | use Module::Pluggable require => 1; | ||||
| 196 | |||||
| 197 | since requiring automatically searches inner packages, which may not be desirable, you can turn this off | ||||
| 198 | |||||
| 199 | |||||
| 200 | package MyClass; | ||||
| 201 | use Module::Pluggable require => 1, inner => 0; | ||||
| 202 | |||||
| 203 | |||||
| 204 | You can limit the plugins loaded using the except option, either as a string, | ||||
| 205 | array ref or regex | ||||
| 206 | |||||
| 207 | package MyClass; | ||||
| 208 | use Module::Pluggable except => 'MyClass::Plugin::Foo'; | ||||
| 209 | |||||
| 210 | or | ||||
| 211 | |||||
| 212 | package MyClass; | ||||
| 213 | use Module::Pluggable except => ['MyClass::Plugin::Foo', 'MyClass::Plugin::Bar']; | ||||
| 214 | |||||
| 215 | or | ||||
| 216 | |||||
| 217 | package MyClass; | ||||
| 218 | use Module::Pluggable except => qr/^MyClass::Plugin::(Foo|Bar)$/; | ||||
| 219 | |||||
| 220 | |||||
| 221 | and similarly for only which will only load plugins which match. | ||||
| 222 | |||||
| 223 | Remember you can use the module more than once | ||||
| 224 | |||||
| 225 | package MyClass; | ||||
| 226 | use Module::Pluggable search_path => 'MyClass::Filters' sub_name => 'filters'; | ||||
| 227 | use Module::Pluggable search_path => 'MyClass::Plugins' sub_name => 'plugins'; | ||||
| 228 | |||||
| 229 | and then later ... | ||||
| 230 | |||||
| 231 | my @filters = $self->filters; | ||||
| 232 | my @plugins = $self->plugins; | ||||
| 233 | |||||
| 234 | =head1 PLUGIN SEARCHING | ||||
| 235 | |||||
| 236 | Every time you call 'plugins' the whole search path is walked again. This allows | ||||
| 237 | for dynamically loading plugins even at run time. However this can get expensive | ||||
| 238 | and so if you don't expect to want to add new plugins at run time you could do | ||||
| 239 | |||||
| 240 | |||||
| 241 | package Foo; | ||||
| 242 | use strict; | ||||
| 243 | use Module::Pluggable sub_name => '_plugins'; | ||||
| 244 | |||||
| 245 | our @PLUGINS; | ||||
| 246 | sub plugins { @PLUGINS ||= shift->_plugins } | ||||
| 247 | 1; | ||||
| 248 | |||||
| 249 | =head1 INNER PACKAGES | ||||
| 250 | |||||
| 251 | If you have, for example, a file B<lib/Something/Plugin/Foo.pm> that | ||||
| 252 | contains package definitions for both C<Something::Plugin::Foo> and | ||||
| 253 | C<Something::Plugin::Bar> then as long as you either have either | ||||
| 254 | the B<require> or B<instantiate> option set then we'll also find | ||||
| 255 | C<Something::Plugin::Bar>. Nifty! | ||||
| 256 | |||||
| 257 | =head1 OPTIONS | ||||
| 258 | |||||
| 259 | You can pass a hash of options when importing this module. | ||||
| 260 | |||||
| 261 | The options can be ... | ||||
| 262 | |||||
| 263 | =head2 sub_name | ||||
| 264 | |||||
| 265 | The name of the subroutine to create in your namespace. | ||||
| 266 | |||||
| 267 | By default this is 'plugins' | ||||
| 268 | |||||
| 269 | =head2 search_path | ||||
| 270 | |||||
| 271 | An array ref of namespaces to look in. | ||||
| 272 | |||||
| 273 | =head2 search_dirs | ||||
| 274 | |||||
| 275 | An array ref of directories to look in before @INC. | ||||
| 276 | |||||
| 277 | =head2 instantiate | ||||
| 278 | |||||
| 279 | Call this method on the class. In general this will probably be 'new' | ||||
| 280 | but it can be whatever you want. Whatever arguments are passed to 'plugins' | ||||
| 281 | will be passed to the method. | ||||
| 282 | |||||
| 283 | The default is 'undef' i.e just return the class name. | ||||
| 284 | |||||
| 285 | =head2 require | ||||
| 286 | |||||
| 287 | Just require the class, don't instantiate (overrides 'instantiate'); | ||||
| 288 | |||||
| 289 | =head2 inner | ||||
| 290 | |||||
| 291 | If set to 0 will B<not> search inner packages. | ||||
| 292 | If set to 1 will override C<require>. | ||||
| 293 | |||||
| 294 | =head2 only | ||||
| 295 | |||||
| 296 | Takes a string, array ref or regex describing the names of the only plugins to | ||||
| 297 | return. Whilst this may seem perverse ... well, it is. But it also | ||||
| 298 | makes sense. Trust me. | ||||
| 299 | |||||
| 300 | =head2 except | ||||
| 301 | |||||
| 302 | Similar to C<only> it takes a description of plugins to exclude | ||||
| 303 | from returning. This is slightly less perverse. | ||||
| 304 | |||||
| 305 | =head2 package | ||||
| 306 | |||||
| 307 | This is for use by extension modules which build on C<Module::Pluggable>: | ||||
| 308 | passing a C<package> option allows you to place the plugin method in a | ||||
| 309 | different package other than your own. | ||||
| 310 | |||||
| 311 | =head2 file_regex | ||||
| 312 | |||||
| 313 | By default C<Module::Pluggable> only looks for I<.pm> files. | ||||
| 314 | |||||
| 315 | By supplying a new C<file_regex> then you can change this behaviour e.g | ||||
| 316 | |||||
| 317 | file_regex => qr/\.plugin$/ | ||||
| 318 | |||||
| 319 | =head2 include_editor_junk | ||||
| 320 | |||||
| 321 | By default C<Module::Pluggable> ignores files that look like they were | ||||
| 322 | left behind by editors. Currently this means files ending in F<~> (~), | ||||
| 323 | the extensions F<.swp> or F<.swo>, or files beginning with F<.#>. | ||||
| 324 | |||||
| 325 | Setting C<include_editor_junk> changes C<Module::Pluggable> so it does | ||||
| 326 | not ignore any files it finds. | ||||
| 327 | |||||
| 328 | =head2 follow_symlinks | ||||
| 329 | |||||
| 330 | Whether, when searching directories, to follow symlinks. | ||||
| 331 | |||||
| 332 | Defaults to 1 i.e do follow symlinks. | ||||
| 333 | |||||
| 334 | =head2 min_depth, max_depth | ||||
| 335 | |||||
| 336 | This will allow you to set what 'depth' of plugin will be allowed. | ||||
| 337 | |||||
| 338 | So, for example, C<MyClass::Plugin::Foo> will have a depth of 3 and | ||||
| 339 | C<MyClass::Plugin::Foo::Bar> will have a depth of 4 so to only get the former | ||||
| 340 | (i.e C<MyClass::Plugin::Foo>) do | ||||
| 341 | |||||
| 342 | package MyClass; | ||||
| 343 | use Module::Pluggable max_depth => 3; | ||||
| 344 | |||||
| 345 | and to only get the latter (i.e C<MyClass::Plugin::Foo::Bar>) | ||||
| 346 | |||||
| 347 | package MyClass; | ||||
| 348 | use Module::Pluggable min_depth => 4; | ||||
| 349 | |||||
| 350 | |||||
| 351 | =head1 TRIGGERS | ||||
| 352 | |||||
| 353 | Various triggers can also be passed in to the options. | ||||
| 354 | |||||
| 355 | If any of these triggers return 0 then the plugin will not be returned. | ||||
| 356 | |||||
| 357 | =head2 before_require <plugin> | ||||
| 358 | |||||
| 359 | Gets passed the plugin name. | ||||
| 360 | |||||
| 361 | If 0 is returned then this plugin will not be required either. | ||||
| 362 | |||||
| 363 | =head2 on_require_error <plugin> <err> | ||||
| 364 | |||||
| 365 | Gets called when there's an error on requiring the plugin. | ||||
| 366 | |||||
| 367 | Gets passed the plugin name and the error. | ||||
| 368 | |||||
| 369 | The default on_require_error handler is to C<carp> the error and return 0. | ||||
| 370 | |||||
| 371 | =head2 on_instantiate_error <plugin> <err> | ||||
| 372 | |||||
| 373 | Gets called when there's an error on instantiating the plugin. | ||||
| 374 | |||||
| 375 | Gets passed the plugin name and the error. | ||||
| 376 | |||||
| 377 | The default on_instantiate_error handler is to C<carp> the error and return 0. | ||||
| 378 | |||||
| 379 | =head2 after_require <plugin> | ||||
| 380 | |||||
| 381 | Gets passed the plugin name. | ||||
| 382 | |||||
| 383 | If 0 is returned then this plugin will be required but not returned as a plugin. | ||||
| 384 | |||||
| 385 | =head1 METHODs | ||||
| 386 | |||||
| 387 | =head2 search_path | ||||
| 388 | |||||
| 389 | The method C<search_path> is exported into you namespace as well. | ||||
| 390 | You can call that at any time to change or replace the | ||||
| 391 | search_path. | ||||
| 392 | |||||
| 393 | $self->search_path( add => "New::Path" ); # add | ||||
| 394 | $self->search_path( new => "New::Path" ); # replace | ||||
| 395 | |||||
| 396 | =head1 BEHAVIOUR UNDER TEST ENVIRONMENT | ||||
| 397 | |||||
| 398 | In order to make testing reliable we exclude anything not from blib if blib.pm is | ||||
| 399 | in %INC. | ||||
| 400 | |||||
| 401 | However if the module being tested used another module that itself used C<Module::Pluggable> | ||||
| 402 | then the second module would fail. This was fixed by checking to see if the caller | ||||
| 403 | had (^|/)blib/ in their filename. | ||||
| 404 | |||||
| 405 | There's an argument that this is the wrong behaviour and that modules should explicitly | ||||
| 406 | trigger this behaviour but that particular code has been around for 7 years now and I'm | ||||
| 407 | reluctant to change the default behaviour. | ||||
| 408 | |||||
| 409 | You can now (as of version 4.1) force Module::Pluggable to look outside blib in a test environment by doing either | ||||
| 410 | |||||
| 411 | require Module::Pluggable; | ||||
| 412 | $Module::Pluggable::FORCE_SEARCH_ALL_PATHS = 1; | ||||
| 413 | import Module::Pluggable; | ||||
| 414 | |||||
| 415 | or | ||||
| 416 | |||||
| 417 | use Module::Pluggable force_search_all_paths => 1; | ||||
| 418 | |||||
| 419 | =head1 @INC hooks and App::FatPacker | ||||
| 420 | |||||
| 421 | If a module's @INC has a hook and that hook is an object which has a C<files()> method then we will | ||||
| 422 | try and require those files too. See C<t/26inc_hook.t> for an example. | ||||
| 423 | |||||
| 424 | This has allowed L<App::FatPacker> (as of version 0.10.0) to provide support for Module::Pluggable. | ||||
| 425 | |||||
| 426 | This should also, theoretically, allow someone to modify PAR to do the same thing. | ||||
| 427 | |||||
| 428 | =head1 FUTURE PLANS | ||||
| 429 | |||||
| 430 | This does everything I need and I can't really think of any other | ||||
| 431 | features I want to add. Famous last words of course (not least | ||||
| 432 | because we're up to version 5.0 at the time of writing). | ||||
| 433 | |||||
| 434 | However suggestions (and patches) are always welcome. | ||||
| 435 | |||||
| 436 | =head1 DEVELOPMENT | ||||
| 437 | |||||
| 438 | The master repo for this module is at | ||||
| 439 | |||||
| 440 | https://github.com/simonwistow/Module-Pluggable | ||||
| 441 | |||||
| 442 | =head1 AUTHOR | ||||
| 443 | |||||
| 444 | Simon Wistow <simon@thegestalt.org> | ||||
| 445 | |||||
| 446 | =head1 COPYING | ||||
| 447 | |||||
| 448 | Copyright, 2006 Simon Wistow | ||||
| 449 | |||||
| 450 | Distributed under the same terms as Perl itself. | ||||
| 451 | |||||
| 452 | =head1 BUGS | ||||
| 453 | |||||
| 454 | None known. | ||||
| 455 | |||||
| 456 | =head1 SEE ALSO | ||||
| 457 | |||||
| 458 | L<File::Spec>, L<File::Find>, L<File::Basename>, L<Class::Factory::Util>, L<Module::Pluggable::Ordered> | ||||
| 459 | |||||
| 460 | =cut | ||||
| 461 | |||||
| 462 |