File Coverage

blib/lib/MooseX/Object/Pluggable.pm
Criterion Covered Total %
statement 70 70 100.0
branch 12 18 66.6
condition n/a
subroutine 17 17 100.0
pod 2 2 100.0
total 101 107 94.3


line stmt bran cond sub pod time code
1             package MooseX::Object::Pluggable;
2             BEGIN {
3 6     6   2737170 $MooseX::Object::Pluggable::AUTHORITY = 'cpan:GRODITI';
4             }
5             # git description: v0.0012-15-gff720b0
6             $MooseX::Object::Pluggable::VERSION = '0.0013';
7             # ABSTRACT: Make your classes pluggable
8              
9 6     6   60 use Carp;
  6         13  
  6         497  
10 6     6   5125 use Moose::Role;
  6         661005  
  6         41  
11 6     6   36208 use Module::Runtime 'use_module';
  6         15  
  6         46  
12 6     6   285 use Scalar::Util 'blessed';
  6         18  
  6         353  
13 6     6   35 use Try::Tiny;
  6         25  
  6         341  
14 6     6   5986 use Module::Pluggable::Object;
  6         48437  
  6         240  
15 6     6   58 use Moose::Util 'find_meta';
  6         12  
  6         84  
16 6     6   7217 use namespace::autoclean;
  6         8599  
  6         37  
17              
18             # =head1 SYNOPSIS
19             #
20             # package MyApp;
21             # use Moose;
22             #
23             # with 'MooseX::Object::Pluggable';
24             #
25             # ...
26             #
27             # package MyApp::Plugin::Pretty;
28             # use Moose::Role;
29             #
30             # sub pretty{ print "I am pretty" }
31             #
32             # 1;
33             #
34             # #
35             # use MyApp;
36             # my $app = MyApp->new;
37             # $app->load_plugin('Pretty');
38             # $app->pretty;
39             #
40             # =head1 DESCRIPTION
41             #
42             # This module is meant to be loaded as a role from Moose-based classes.
43             # It will add five methods and four attributes to assist you with the loading
44             # and handling of plugins and extensions for plugins. I understand that this may
45             # pollute your namespace, however I took great care in using the least ambiguous
46             # names possible.
47             #
48             # =head1 How plugins Work
49             #
50             # Plugins and extensions are just Roles by a fancy name. They are loaded at runtime
51             # on demand and are instance, not class based. This means that if you have more than
52             # one instance of a class they can all have different plugins loaded. This is a feature.
53             #
54             # Plugin methods are allowed to C<around>, C<before>, C<after>
55             # their consuming classes, so it is important to watch for load order as plugins can
56             # and will overload each other. You may also add attributes through C<has>.
57             #
58             # Please note that when you load at runtime you lose the ability to wrap C<BUILD>
59             # and roles using C<has> will not go through compile time checks like C<required>
60             # and C<default>.
61             #
62             # Even though C<override> will work, I B<STRONGLY> discourage its use
63             # and a warning will be thrown if you try to use it.
64             # This is closely linked to the way multiple roles being applied is handled and is not
65             # likely to change. C<override> behavior is closely linked to inheritance and thus will
66             # likely not work as you expect it in multiple inheritance situations. Point being,
67             # save yourself the headache.
68             #
69             # =head1 How plugins are loaded
70             #
71             # When roles are applied at runtime an anonymous class will wrap your class and
72             # C<< $self->blessed >>, C<< ref $self >> and C<< $self->meta->name >>
73             # will no longer return the name of your object;
74             # they will instead return the name of the anonymous class created at runtime.
75             # See C<_original_class_name>.
76             #
77             # =head1 Usage
78             #
79             # For a simple example see the tests included in this distribution.
80             #
81             # =head1 Attributes
82             #
83             # =head2 _plugin_ns
84             #
85             # String. The prefix to use for plugin names provided. C<MyApp::Plugin> is sensible.
86             #
87             # =head2 _plugin_app_ns
88             #
89             # An ArrayRef accessor that automatically dereferences into array on a read call.
90             # By default it will be filled with the class name and its precedents. It is used
91             # to determine which directories to look for plugins as well as which plugins
92             # take precedence upon namespace collisions. This allows you to subclass a pluggable
93             # class and still use its plugins while using yours first if they are available.
94             #
95             # =head2 _plugin_locator
96             #
97             # An automatically built instance of L<Module::Pluggable::Object> used to locate
98             # available plugins.
99             #
100             # =head2 _original_class_name
101             #
102             # =for stopwords instantiation
103             #
104             # Because of the way roles apply, C<< $self->blessed >>, C<< ref $self >>
105             # and C<< $self->meta->name >> will
106             # no longer return what you expect. Instead, upon instantiation, the name of the
107             # class instantiated will be stored in this attribute if you need to access the
108             # name the class held before any runtime roles were applied.
109             #
110             # =cut
111              
112             #--------#---------#---------#---------#---------#---------#---------#---------#
113              
114             has _plugin_ns => (
115             is => 'rw',
116             required => 1,
117             isa => 'Str',
118             default => sub{ 'Plugin' },
119             );
120              
121             has _original_class_name => (
122             is => 'ro',
123             required => 1,
124             isa => 'Str',
125             default => sub{ blessed($_[0]) },
126             );
127              
128             has _plugin_loaded => (
129             is => 'rw',
130             required => 1,
131             isa => 'HashRef',
132             default => sub{ {} }
133             );
134              
135             has _plugin_app_ns => (
136             is => 'rw',
137             required => 1,
138             isa => 'ArrayRef',
139             lazy => 1,
140             auto_deref => 1,
141             builder => '_build_plugin_app_ns',
142             trigger => sub{ $_[0]->_clear_plugin_locator if $_[0]->_has_plugin_locator; },
143             );
144              
145             has _plugin_locator => (
146             is => 'rw',
147             required => 1,
148             lazy => 1,
149             isa => 'Module::Pluggable::Object',
150             clearer => '_clear_plugin_locator',
151             predicate => '_has_plugin_locator',
152             builder => '_build_plugin_locator'
153             );
154              
155             #--------#---------#---------#---------#---------#---------#---------#---------#
156              
157             # =head1 Public Methods
158             #
159             # =head2 load_plugins @plugins
160             #
161             # =head2 load_plugin $plugin
162             #
163             # Load the appropriate role for C<$plugin>.
164             #
165             # =cut
166              
167             sub load_plugins {
168 18     18 1 1888 my ($self, @plugins) = @_;
169 18 50       80 die("You must provide a plugin name") unless @plugins;
170              
171 18         1014 my $loaded = $self->_plugin_loaded;
172 18         52 @plugins = grep { not exists $loaded->{$_} } @plugins;
  19         94  
173              
174 18 100       90 return if @plugins == 0;
175              
176 17         101 foreach my $plugin (@plugins)
177             {
178 18         151 my $role = $self->_role_from_plugin($plugin);
179 18 50       153 return if not $self->_load_and_apply_role($role);
180              
181 15         98 $loaded->{$plugin} = $role;
182             }
183              
184 14         130 return 1;
185             }
186              
187              
188             sub load_plugin {
189 17     17 1 13149 my $self = shift;
190 17         147 $self->load_plugins(@_);
191             }
192              
193             # =head1 Private Methods
194             #
195             # There's nothing stopping you from using these, but if you are using them
196             # for anything that's not really complicated you are probably doing
197             # something wrong.
198             #
199             # =head2 _role_from_plugin $plugin
200             #
201             # Creates a role name from a plugin name. If the plugin name is prepended
202             # with a C<+> it will be treated as a full name returned as is. Otherwise
203             # a string consisting of C<$plugin> prepended with the C<_plugin_ns>
204             # and the first valid value from C<_plugin_app_ns> will be returned. Example
205             #
206             # #assuming appname MyApp and C<_plugin_ns> 'Plugin'
207             # $self->_role_from_plugin("MyPlugin"); # MyApp::Plugin::MyPlugin
208             #
209             # =cut
210              
211             sub _role_from_plugin{
212 30     30   8225 my ($self, $plugin) = @_;
213              
214 30 100       173 return $1 if( $plugin =~ /^\+(.*)/ );
215              
216 21         950 my $o = join '::', $self->_plugin_ns, $plugin;
217             #Father, please forgive me for I have sinned.
218 21         962 my @roles = grep{ /${o}$/ } $self->_plugin_locator->plugins;
  95         61261  
219              
220 21 50       109 croak("Unable to locate plugin '$plugin'") unless @roles;
221 21 100       123 return $roles[0] if @roles == 1;
222              
223 5         15 my $i = 0;
224 5         285 my %precedence_list = map{ $i++; "${_}::${o}", $i } $self->_plugin_app_ns;
  12         21  
  12         49  
225              
226 5         35 @roles = sort{ $precedence_list{$a} <=> $precedence_list{$b}} @roles;
  9         24  
227              
228 5         40 return shift @roles;
229             }
230              
231             # =head2 _load_and_apply_role @roles
232             #
233             # Require C<$role> if it is not already loaded and apply it. This is
234             # the meat of this module.
235             #
236             # =cut
237              
238             sub _load_and_apply_role {
239 18     18   41 my ($self, $role) = @_;
240 18 50       65 die("You must provide a role name") unless $role;
241              
242 18     18   955 try { use_module($role) }
243 18     3   223 catch { confess("Failed to load role: ${role} $_") };
  3         476952  
244              
245 15 50       562997 croak("Your plugin '$role' must be a Moose::Role")
246             unless find_meta($role)->isa('Moose::Meta::Role');
247              
248 15         110 carp("Using 'override' is strongly discouraged and may not behave ".
249             "as you expect it to. Please use 'around'")
250 15 50       248 if scalar keys %{ $role->meta->get_override_method_modifiers_map };
251              
252 15         1242 Moose::Util::apply_all_roles( $self, $role );
253              
254 15         163619 return 1;
255             }
256              
257             # =head2 _build_plugin_app_ns
258             #
259             # Automatically builds the _plugin_app_ns attribute with the classes in the
260             # class precedence list that are not part of Moose.
261             #
262             # =cut
263              
264             sub _build_plugin_app_ns{
265 6     6   16 my $self = shift;
266 6         47 my @names = (grep {$_ !~ /^Moose::/} $self->meta->class_precedence_list);
  14         1422  
267 6         270 return \@names;
268             }
269              
270             # =head2 _build_plugin_locator
271             #
272             # Automatically creates a L<Module::Pluggable::Object> instance with the correct
273             # search_path.
274             #
275             # =cut
276              
277             sub _build_plugin_locator{
278 7     7   16 my $self = shift;
279              
280 11         469 my $locator = Module::Pluggable::Object->new
281             ( search_path =>
282 7         319 [ map { join '::', ($_, $self->_plugin_ns) } $self->_plugin_app_ns ]
283             );
284 7         354 return $locator;
285             }
286              
287             # =head2 meta
288             #
289             # Keep tests happy. See L<Moose>
290             #
291             # =cut
292              
293             1;
294              
295             =pod
296              
297             =encoding UTF-8
298              
299             =for :stopwords Guillermo Roditi <groditi@cpan.org> David Hepler Yuval Kogman Steinbrunner
300             Karen Etheridge Robert Boone Shawn M Moore Todd instantiation AnnoCPAN
301             Stevan
302              
303             =head1 NAME
304              
305             MooseX::Object::Pluggable - Make your classes pluggable
306              
307             =head1 VERSION
308              
309             version 0.0013
310              
311             =head1 SYNOPSIS
312              
313             package MyApp;
314             use Moose;
315              
316             with 'MooseX::Object::Pluggable';
317              
318             ...
319              
320             package MyApp::Plugin::Pretty;
321             use Moose::Role;
322              
323             sub pretty{ print "I am pretty" }
324              
325             1;
326              
327             #
328             use MyApp;
329             my $app = MyApp->new;
330             $app->load_plugin('Pretty');
331             $app->pretty;
332              
333             =head1 DESCRIPTION
334              
335             This module is meant to be loaded as a role from Moose-based classes.
336             It will add five methods and four attributes to assist you with the loading
337             and handling of plugins and extensions for plugins. I understand that this may
338             pollute your namespace, however I took great care in using the least ambiguous
339             names possible.
340              
341             =head1 How plugins Work
342              
343             Plugins and extensions are just Roles by a fancy name. They are loaded at runtime
344             on demand and are instance, not class based. This means that if you have more than
345             one instance of a class they can all have different plugins loaded. This is a feature.
346              
347             Plugin methods are allowed to C<around>, C<before>, C<after>
348             their consuming classes, so it is important to watch for load order as plugins can
349             and will overload each other. You may also add attributes through C<has>.
350              
351             Please note that when you load at runtime you lose the ability to wrap C<BUILD>
352             and roles using C<has> will not go through compile time checks like C<required>
353             and C<default>.
354              
355             Even though C<override> will work, I B<STRONGLY> discourage its use
356             and a warning will be thrown if you try to use it.
357             This is closely linked to the way multiple roles being applied is handled and is not
358             likely to change. C<override> behavior is closely linked to inheritance and thus will
359             likely not work as you expect it in multiple inheritance situations. Point being,
360             save yourself the headache.
361              
362             =head1 How plugins are loaded
363              
364             When roles are applied at runtime an anonymous class will wrap your class and
365             C<< $self->blessed >>, C<< ref $self >> and C<< $self->meta->name >>
366             will no longer return the name of your object;
367             they will instead return the name of the anonymous class created at runtime.
368             See C<_original_class_name>.
369              
370             =head1 Usage
371              
372             For a simple example see the tests included in this distribution.
373              
374             =head1 Attributes
375              
376             =head2 _plugin_ns
377              
378             String. The prefix to use for plugin names provided. C<MyApp::Plugin> is sensible.
379              
380             =head2 _plugin_app_ns
381              
382             An ArrayRef accessor that automatically dereferences into array on a read call.
383             By default it will be filled with the class name and its precedents. It is used
384             to determine which directories to look for plugins as well as which plugins
385             take precedence upon namespace collisions. This allows you to subclass a pluggable
386             class and still use its plugins while using yours first if they are available.
387              
388             =head2 _plugin_locator
389              
390             An automatically built instance of L<Module::Pluggable::Object> used to locate
391             available plugins.
392              
393             =head2 _original_class_name
394              
395             Because of the way roles apply, C<< $self->blessed >>, C<< ref $self >>
396             and C<< $self->meta->name >> will
397             no longer return what you expect. Instead, upon instantiation, the name of the
398             class instantiated will be stored in this attribute if you need to access the
399             name the class held before any runtime roles were applied.
400              
401             =head1 Public Methods
402              
403             =head2 load_plugins @plugins
404              
405             =head2 load_plugin $plugin
406              
407             Load the appropriate role for C<$plugin>.
408              
409             =head1 Private Methods
410              
411             There's nothing stopping you from using these, but if you are using them
412             for anything that's not really complicated you are probably doing
413             something wrong.
414              
415             =head2 _role_from_plugin $plugin
416              
417             Creates a role name from a plugin name. If the plugin name is prepended
418             with a C<+> it will be treated as a full name returned as is. Otherwise
419             a string consisting of C<$plugin> prepended with the C<_plugin_ns>
420             and the first valid value from C<_plugin_app_ns> will be returned. Example
421              
422             #assuming appname MyApp and C<_plugin_ns> 'Plugin'
423             $self->_role_from_plugin("MyPlugin"); # MyApp::Plugin::MyPlugin
424              
425             =head2 _load_and_apply_role @roles
426              
427             Require C<$role> if it is not already loaded and apply it. This is
428             the meat of this module.
429              
430             =head2 _build_plugin_app_ns
431              
432             Automatically builds the _plugin_app_ns attribute with the classes in the
433             class precedence list that are not part of Moose.
434              
435             =head2 _build_plugin_locator
436              
437             Automatically creates a L<Module::Pluggable::Object> instance with the correct
438             search_path.
439              
440             =head2 meta
441              
442             Keep tests happy. See L<Moose>
443              
444             =head1 SEE ALSO
445              
446             L<Moose>, L<Moose::Role>, L<Class::Inspector>
447              
448             =head1 BUGS
449              
450             Holler?
451              
452             Please report any bugs or feature requests to
453             C<bug-MooseX-Object-Pluggable at rt.cpan.org>, or through the web interface at
454             L<http://rt.cpan.org/Public/Dist/Display.html?Name=MooseX-Object-Pluggable>.
455             I will be notified, and then you'll automatically be notified of progress on
456             your bug as I make changes.
457              
458             =head1 SUPPORT
459              
460             You can find documentation for this module with the perldoc command.
461              
462             perldoc MooseX-Object-Pluggable
463              
464             You can also look for information at:
465              
466             =over 4
467              
468             =item * AnnoCPAN: Annotated CPAN documentation
469              
470             L<http://annocpan.org/dist/MooseX-Object-Pluggable>
471              
472             =item * CPAN Ratings
473              
474             L<http://cpanratings.perl.org/d/MooseX-Object-Pluggable>
475              
476             =item * RT: CPAN's request tracker
477              
478             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=MooseX-Object-Pluggable>
479              
480             =item * Search CPAN
481              
482             L<http://search.cpan.org/dist/MooseX-Object-Pluggable>
483              
484             =back
485              
486             =head1 ACKNOWLEDGEMENTS
487              
488             =over 4
489              
490             =item #Moose - Huge number of questions
491              
492             =item Matt S Trout <mst@shadowcatsystems.co.uk> - ideas / planning.
493              
494             =item Stevan Little - EVERYTHING. Without him this would have never happened.
495              
496             =item Shawn M Moore - bugfixes
497              
498             =back
499              
500             =head1 AUTHOR
501              
502             Guillermo Roditi <groditi@cpan.org>
503              
504             =head1 COPYRIGHT AND LICENSE
505              
506             This software is copyright (c) 2007 by Guillermo Roditi <groditi@cpan.org>.
507              
508             This is free software; you can redistribute it and/or modify it under
509             the same terms as the Perl 5 programming language system itself.
510              
511             =head1 CONTRIBUTORS
512              
513             =over 4
514              
515             =item *
516              
517             David Steinbrunner <dsteinbrunner@pobox.com>
518              
519             =item *
520              
521             Karen Etheridge <ether@cpan.org>
522              
523             =item *
524              
525             Robert Boone <robo4288@gmail.com>
526              
527             =item *
528              
529             Shawn M Moore <sartak@gmail.com>
530              
531             =item *
532              
533             Todd Hepler <thepler@employees.org>
534              
535             =item *
536              
537             Yuval Kogman <nothingmuch@woobling.org>
538              
539             =back
540              
541             =cut
542              
543             __END__;
544