File Coverage

blib/lib/MooseX/Object/Pluggable.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


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