File Coverage

blib/lib/MooX/Role/Pluggable.pm
Criterion Covered Total %
statement 240 328 73.1
branch 89 164 54.2
condition 14 29 48.2
subroutine 36 42 85.7
pod 16 16 100.0
total 395 579 68.2


line stmt bran cond sub pod time code
1             package MooX::Role::Pluggable;
2             $MooX::Role::Pluggable::VERSION = '1.002001';
3 2     2   14574 use Carp;
  2         5  
  2         132  
4 2     2   8 use strictures 2;
  2         12  
  2         58  
5              
6 2     2   467 use Scalar::Util 'blessed';
  2         2  
  2         175  
7 2     2   965 use Try::Tiny;
  2         1910  
  2         102  
8              
9 2     2   978 use Types::Standard -all;
  2         103989  
  2         29  
10              
11 2     2   58773 use MooX::Role::Pluggable::Constants;
  2         4  
  2         162  
12              
13              
14 2     2   9 use Moo::Role;
  2         3  
  2         19  
15              
16              
17             has __pluggable_opts => (
18             lazy => 1,
19             is => 'ro',
20             isa => Dict[
21             reg_prefix => Str,
22             ev_prefix => Str,
23             types => HashRef,
24             ],
25             builder => sub {
26             +{
27 2     2   1380 reg_prefix => 'plugin_',
28             ev_prefix => 'plugin_ev_',
29             types => +{ PROCESS => 'P', NOTIFY => 'N' },
30             },
31             },
32             );
33              
34             has __pluggable_loaded => (
35             lazy => 1,
36             is => 'ro',
37             isa => Dict[
38             ALIAS => HashRef,
39             OBJ => HashRef,
40             HANDLE => HashRef,
41             ],
42             builder => sub {
43             +{
44 2     2   1420 ALIAS => +{}, # Objs keyed by aliases
45             OBJ => +{}, # Aliases keyed by obj
46             HANDLE => +{}, # Type/event map hashes keyed by obj
47             },
48             },
49             );
50              
51             has __pluggable_pipeline => (
52             lazy => 1,
53             is => 'ro',
54             isa => ArrayRef,
55 2     2   1040 builder => sub { [] },
56             );
57              
58              
59             sub _pluggable_destroy {
60 2     2   9 my ($self) = @_;
61 2         10 $self->plugin_del($_) for $self->plugin_alias_list;
62             }
63              
64 0     0   0 sub _pluggable_event {
65             # This should be overriden to handle Pluggable events
66             # ( plugin_{added, removed, error} )
67             }
68              
69             sub _pluggable_init {
70 2     2   241 my ($self, %params) = @_;
71 2         13 $params{lc $_} = delete $params{$_} for keys %params;
72              
73 2 50       8 my $reg_prefix = defined $params{register_prefix} ?
74             $params{register_prefix} : $params{reg_prefix};
75 2 100       8 $self->__pluggable_opts->{reg_prefix} = $reg_prefix
76             if defined $reg_prefix;
77              
78 2 50       27 my $ev_prefix = defined $params{event_prefix} ?
79             $params{event_prefix} : $params{ev_prefix};
80 2 50       6 $self->__pluggable_opts->{ev_prefix} = $ev_prefix
81             if defined $ev_prefix;
82              
83 2 50       4 if (defined $params{types}) {
84             $self->__pluggable_opts->{types} =
85             ref $params{types} eq 'ARRAY' ?
86 2 100       190 +{ map {; $_ => $_ } @{ $params{types} } }
  0 50       0  
  0         0  
87             : ref $params{types} eq 'HASH' ?
88             $params{types}
89             : confess 'Expected ARRAY or HASH but got '.$params{types};
90             }
91              
92             $self
93 1         24 }
94              
95             sub _pluggable_process {
96 24     24   2850 my ($self, $type, $event, $args) = @_;
97              
98             # This is essentially the same logic as Object::Pluggable.
99             # Profiled, rewritten, and tightened up a bit;
100             #
101             # - Error handling is much faster as a normal sub
102             # Still need $self to dispatch _pluggable_event, but skipping method
103             # resolution and passing $self on the arg stack added a few hundred
104             # extra calls/sec, and override seems like an acceptable sacrifice
105             # Additionally our error handler is optimized
106             #
107             # - Do not invoke the regex engine at all, saving a fair bit of
108             # time; checking index() and applying substr() as needed to strip
109             # event prefixes is significantly quicker.
110             #
111             # - Conditionals have been optimized a bit.
112             #
113             # I'm open to other ideas . . .
114 24 100       54 unless (ref $args) {
115 1         138 confess 'Expected a type, event, and (possibly empty) args ARRAY'
116             }
117              
118 23         51 my $prefix = $self->__pluggable_opts->{ev_prefix};
119 23 100       579 substr($event, 0, length($prefix), '') if index($event, $prefix) == 0;
120              
121 23         50 my $meth = $self->__pluggable_opts->{types}->{$type} .'_'. $event;
122              
123 23         510 my ($retval, $self_ret, @extra) = EAT_NONE;
124              
125 23         24 local $@;
126 23 100       117 if ( $self->can($meth) ) {
    100          
127             # Dispatch to ourself
128 17         18 eval {;
129 17         52 $self_ret = $self->$meth($self, \(@$args), \@extra)
130             };
131 17         128 __plugin_process_chk($self, $self, $meth, $self_ret);
132             } elsif ( $self->can('_default') ) {
133             # Dispatch to _default
134 2         2 eval {;
135 2         6 $self_ret = $self->_default($self, $meth, \(@$args), \@extra)
136             };
137 2         13 __plugin_process_chk($self, $self, '_default', $self_ret);
138             }
139              
140 23 100       82 if (! defined $self_ret) {
    50          
    50          
    100          
141             # No-op.
142             } elsif ( $self_ret == EAT_PLUGIN ) {
143             # Don't plugin-process, just return EAT_NONE.
144             # (Higher levels like Emitter can still pick this up.)
145 0         0 return $retval
146             } elsif ( $self_ret == EAT_CLIENT ) {
147             # Plugin process, but return EAT_ALL after.
148 0         0 $retval = EAT_ALL
149             } elsif ( $self_ret == EAT_ALL ) {
150 15         34 return EAT_ALL
151             }
152              
153 8 50       16 if (@extra) {
154 0         0 push @$args, splice @extra, 0, scalar(@extra)
155             }
156              
157 8         22 my $handle_ref = $self->__pluggable_loaded->{HANDLE};
158 8         214 my $plug_ret;
159 8         10 PLUG: for my $thisplug (
160 8         17 grep {;
161 8 100 66     191 exists $handle_ref->{$_}->{$type}->{$event}
162             || exists $handle_ref->{$_}->{$type}->{all}
163             && $self != $_
164             } @{ $self->__pluggable_pipeline } ) {
165 6         6 undef $plug_ret;
166             # Using by_ref is nicer, but the method call is too much overhead.
167 6         20 my $this_alias = $self->__pluggable_loaded->{OBJ}->{$thisplug};
168              
169 6 100       170 if ( $thisplug->can($meth) ) {
    50          
170 5         4 eval {;
171 5         17 $plug_ret = $thisplug->$meth($self, \(@$args), \@extra)
172             };
173 5         35 __plugin_process_chk($self, $thisplug, $meth, $plug_ret, $this_alias);
174             } elsif ( $thisplug->can('_default') ) {
175 1         2 eval {;
176 1         3 $plug_ret = $thisplug->_default($self, $meth, \(@$args), \@extra)
177             };
178 1         13 __plugin_process_chk($self, $thisplug, '_default', $plug_ret, $this_alias);
179             }
180              
181 6 50       27 if (! defined $plug_ret) {
    100          
    50          
    50          
182             # No-op.
183             } elsif ($plug_ret == EAT_PLUGIN) {
184             # Stop plugin-processing.
185             # Return EAT_ALL if we previously had a EAT_CLIENT
186             # Return EAT_NONE otherwise
187 1         3 return $retval
188             } elsif ($plug_ret == EAT_CLIENT) {
189             # Set a pending EAT_ALL.
190             # If another plugin in the pipeline returns EAT_PLUGIN,
191             # we'll tell higher layers like Emitter to EAT_ALL
192 0         0 $retval = EAT_ALL
193             } elsif ($plug_ret == EAT_ALL) {
194 0         0 return EAT_ALL
195             }
196              
197 5 50       15 if (@extra) {
198 0         0 push @$args, splice @extra, 0, scalar(@extra);
199             }
200              
201             } # PLUG
202              
203             $retval
204 7         74 }
205              
206             sub __plugin_process_chk {
207             # Ugly as sin, but fast if there are no errors, which matters here.
208              
209 25 100 100 25   168 if ($@) {
    50 66        
      66        
      33        
210 1         3 chomp $@;
211 1         2 my ($self, $obj, $meth, undef, $src) = @_;
212              
213 1 50       2 my $e_src = defined $src ? "plugin '$src'" : 'self' ;
214 1         2 my $err = "$meth call on $e_src failed: $@";
215              
216 1         4 warn "$err\n";
217              
218 1         3 $self->_pluggable_event(
219             $self->__pluggable_opts->{ev_prefix} . "plugin_error",
220             $err,
221             $obj,
222             $e_src
223             );
224              
225             return
226 1         4 } elsif (! defined $_[3] ||
227             ( $_[3] != EAT_NONE && $_[3] != EAT_ALL &&
228             $_[3] != EAT_CLIENT && $_[3] != EAT_PLUGIN ) ) {
229              
230 0         0 my ($self, $obj, $meth, undef, $src) = @_;
231              
232 0 0       0 my $e_src = defined $src ? "plugin '$src'" : 'self' ;
233 0         0 my $err = "$meth call on $e_src did not return a valid EAT_ constant";
234              
235 0         0 warn "$err\n";
236              
237 0         0 $self->_pluggable_event(
238             $self->__pluggable_opts->{ev_prefix} . "plugin_error",
239             $err,
240             $obj,
241             $e_src
242             );
243              
244             return
245 0         0 }
246             }
247              
248              
249             ## Basic plugin manipulation (add/del/get/replace ...)
250              
251             sub plugin_add {
252 4     4 1 19605 my ($self, $alias, $plugin, @args) = @_;
253              
254 4 50 33     45 confess "Expected a plugin alias and object"
255             unless defined $alias and blessed $plugin;
256              
257 4         14 $self->plugin_pipe_push($alias, $plugin, @args)
258             }
259              
260             sub plugin_alias_list {
261 3     3 1 216 my ($self) = @_;
262 7         164 map {; $self->__pluggable_loaded->{OBJ}->{$_} }
  3         12  
263 3         4 @{ $self->__pluggable_pipeline }
264             }
265              
266             sub plugin_del {
267 5     5 1 76 my ($self, $alias_or_plug, @args) = @_;
268              
269 5 50       13 confess "Expected a plugin alias"
270             unless defined $alias_or_plug;
271              
272 5         14 scalar( $self->__plugin_pipe_remove($alias_or_plug, @args) )
273             }
274              
275             sub plugin_get {
276 2     2 1 12 my ($self, $item) = @_;
277              
278 2         6 my ($item_alias, $item_plug) = $self->__plugin_get_plug_any($item);
279              
280 2 50       56 unless (defined $item_plug) {
281 0         0 carp ($@ = "No such plugin: $item_alias");
282             return
283 0         0 }
284              
285 2 100       12 wantarray ? ($item_plug, $item_alias) : $item_plug
286             }
287              
288             sub plugin_replace {
289 1     1 1 296 my ($self, %params) = @_;
290 1         8 $params{lc $_} = delete $params{$_} for keys %params;
291              
292             # ->plugin_replace(
293             # old => $obj || $alias,
294             # alias => $newalias,
295             # plugin => $newplug,
296             # # optional:
297             # unregister_args => ARRAY
298             # register_args => ARRAY
299             # )
300              
301 1         3 for (qw/old alias plugin/) {
302 3 50       8 confess "Missing required param $_"
303             unless defined $params{$_}
304             }
305              
306 1         3 my ($old_alias, $old_plug)
307             = $self->__plugin_get_plug_any( $params{old} );
308              
309 1 50       32 unless (defined $old_plug) {
310 0         0 $@ = "No such plugin: $old_alias";
311 0         0 carp $@;
312             return
313 0         0 }
314              
315 0         0 my @unreg_args = ref $params{unregister_args} eq 'ARRAY' ?
316 1 50       4 @{ $params{unregister_args} } : () ;
317              
318 1         3 $self->__plug_pipe_unregister( $old_alias, $old_plug, @unreg_args );
319              
320 1         2 my ($new_alias, $new_plug) = @params{'alias','plugin'};
321              
322 0         0 return unless $self->__plug_pipe_register( $new_alias, $new_plug,
323             (
324             ref $params{register_args} eq 'ARRAY' ?
325 1 50       7 @{ $params{register_args} } : ()
    50          
326             ),
327             );
328              
329 1         2 for my $thisplug (@{ $self->__pluggable_pipeline }) {
  1         4  
330 1 50       25 if ($thisplug == $old_plug) {
331 1         2 $thisplug = $params{plugin};
332             last
333 1         3 }
334             }
335              
336             $old_plug
337 1         4 }
338              
339              
340             ## Event registration.
341              
342             sub subscribe {
343 5     5 1 45 my ($self, $plugin, $type, @events) = @_;
344              
345 5 50       24 confess "Cannot subscribe; event type $type not supported"
346             unless exists $self->__pluggable_opts->{types}->{$type};
347              
348 5 50       157 confess "Expected a plugin object, a type, and a list of events"
349             unless @events;
350              
351 5 50       19 confess "Expected a blessed plugin object" unless blessed $plugin;
352              
353 5   50     13 my $handles
354             = $self->__pluggable_loaded->{HANDLE}->{$plugin}->{$type}
355             ||= +{};
356              
357 5         185 for my $ev (@events) {
358 6 50       20 if (ref $ev eq 'ARRAY') {
359 0         0 $handles->{$_} = 1 for @$ev;
360             next
361 0         0 }
362 6         18 $handles->{$ev} = 1
363             }
364              
365             1
366 5         16 }
367              
368             sub unsubscribe {
369 0     0 1 0 my ($self, $plugin, $type, @events) = @_;
370              
371 0 0       0 confess "Cannot unsubscribe; event type $type not supported"
372             unless exists $self->__pluggable_opts->{types}->{$type};
373              
374 0 0 0     0 confess "Expected a blessed plugin obj, event type, and events to unsubscribe"
375             unless blessed $plugin and defined $type;
376              
377 0 0       0 confess "No events specified; did you mean to plugin_del instead?"
378             unless @events;
379              
380 0   0     0 my $handles =
381             $self->__pluggable_loaded->{HANDLE}->{$plugin}->{$type} || +{};
382              
383 0         0 for my $ev (@events) {
384 0 0       0 if (ref $ev eq 'ARRAY') {
385 0         0 for my $this_ev (@$ev) {
386 0 0       0 unless (delete $handles->{$this_ev}) {
387 0         0 carp "Nonexistant event $this_ev cannot be unsubscribed from";
388             }
389             }
390             } else {
391 0 0       0 unless (delete $handles->{$ev}) {
392 0         0 carp "Nonexistant event $ev cannot be unsubscribed from";
393             }
394             }
395              
396             }
397              
398             1
399 0         0 }
400              
401              
402             ## Pipeline methods.
403              
404             sub plugin_pipe_push {
405 4     4 1 8 my ($self, $alias, $plug, @args) = @_;
406              
407 4 50       14 if (my $existing = $self->__plugin_by_alias($alias) ) {
408 0         0 $@ = "Already have plugin $alias : $existing";
409 0         0 carp $@;
410             return
411 0         0 }
412              
413 4 50       140 return unless $self->__plug_pipe_register($alias, $plug, @args);
414              
415 4         3 push @{ $self->__pluggable_pipeline }, $plug;
  4         11  
416              
417 4         78 scalar @{ $self->__pluggable_pipeline }
  4         9  
418             }
419              
420             sub plugin_pipe_pop {
421 0     0 1 0 my ($self, @args) = @_;
422              
423 0 0       0 return unless @{ $self->__pluggable_pipeline };
  0         0  
424              
425 0         0 my $plug = pop @{ $self->__pluggable_pipeline };
  0         0  
426 0         0 my $alias = $self->__plugin_by_ref($plug);
427              
428 0         0 $self->__plug_pipe_unregister($alias, $plug, @args);
429              
430 0 0       0 wantarray ? ($plug, $alias) : $plug
431             }
432              
433             sub plugin_pipe_unshift {
434 1     1 1 3 my ($self, $alias, $plug, @args) = @_;
435              
436 1 50       4 if (my $existing = $self->__plugin_by_alias($alias) ) {
437 0         0 $@ = "Already have plugin $alias : $existing";
438 0         0 carp $@;
439             return
440 0         0 }
441              
442 1 50       35 return unless $self->__plug_pipe_register($alias, $plug, @args);
443              
444 1         1 unshift @{ $self->__pluggable_pipeline }, $plug;
  1         3  
445              
446 1         52 scalar @{ $self->__pluggable_pipeline }
  1         4  
447             }
448              
449             sub plugin_pipe_shift {
450 1     1 1 2 my ($self, @args) = @_;
451              
452 1 50       1 return unless @{ $self->__pluggable_pipeline };
  1         3  
453              
454 1         30 my $plug = shift @{ $self->__pluggable_pipeline };
  1         3  
455 1         22 my $alias = $self->__plugin_by_ref($plug);
456              
457 1         65 $self->__plug_pipe_unregister($alias, $plug, @args);
458              
459 1 50       6 wantarray ? ($plug, $alias) : $plug
460             }
461              
462             sub __plugin_pipe_remove {
463 5     5   8 my ($self, $old, @unreg_args) = @_;
464              
465 5         13 my ($old_alias, $old_plug) = $self->__plugin_get_plug_any($old);
466              
467 5 50       139 unless (defined $old_plug) {
468 0         0 $@ = "No such plugin: $old_alias";
469 0         0 carp $@;
470             return
471 0         0 }
472              
473 5         7 my $idx = 0;
474 5         4 for my $thisplug (@{ $self->__pluggable_pipeline }) {
  5         12  
475 5 50       105 if ($thisplug == $old_plug) {
476 5         5 splice @{ $self->__pluggable_pipeline }, $idx, 1;
  5         10  
477             last
478 5         93 }
479 0         0 ++$idx;
480             }
481              
482 5         13 $self->__plug_pipe_unregister( $old_alias, $old_plug, @unreg_args );
483              
484 5 50       42 wantarray ? ($old_plug, $old_alias) : $old_plug
485             }
486              
487             sub plugin_pipe_get_index {
488 9     9 1 246 my ($self, $item) = @_;
489              
490 9         19 my ($item_alias, $item_plug) = $self->__plugin_get_plug_any($item);
491              
492 9 50       267 unless (defined $item_plug) {
493 0         0 $@ = "No such plugin: $item_alias";
494 0         0 carp $@;
495 0         0 return -1
496             }
497              
498 9         10 my $idx = 0;
499 9         11 for my $thisplug (@{ $self->__pluggable_pipeline }) {
  9         16  
500 13 100       239 return $idx if $thisplug == $item_plug;
501 4         5 $idx++;
502             }
503              
504 0         0 return -1
505             }
506              
507             sub plugin_pipe_insert_before {
508 1     1 1 4 my ($self, %params) = @_;
509 1         7 $params{lc $_} = delete $params{$_} for keys %params;
510             # ->insert_before(
511             # before =>
512             # alias =>
513             # plugin =>
514             # register_args =>
515             # );
516              
517 1         55 for (qw/before alias plugin/) {
518 3 50       9 confess "Missing required param $_"
519             unless defined $params{$_}
520             }
521              
522 1         6 my ($prev_alias, $prev_plug)
523             = $self->__plugin_get_plug_any( $params{before} );
524              
525 1 50       33 unless (defined $prev_plug) {
526 0         0 $@ = "No such plugin: $prev_alias";
527 0         0 carp $@;
528             return
529 0         0 }
530              
531 1 50       3 if ( my $existing = $self->__plugin_by_alias($params{alias}) ) {
532 0         0 $@ = "Already have plugin $params{alias} : $existing";
533 0         0 carp $@;
534             return
535 0         0 }
536              
537 0         0 return unless $self->__plug_pipe_register(
538             $params{alias}, $params{plugin},
539             (
540             ref $params{register_args} eq 'ARRAY' ?
541 1 50       26 @{ $params{register_args} } : ()
    50          
542             )
543             );
544              
545 1         3 my $idx = 0;
546 1         1 for my $thisplug (@{ $self->__pluggable_pipeline }) {
  1         3  
547 2 100       31 if ($thisplug == $prev_plug) {
548 1         2 splice @{ $self->__pluggable_pipeline }, $idx, 0, $params{plugin};
  1         2  
549             last
550 1         20 }
551 1         1 $idx++;
552             }
553              
554             1
555 1         5 }
556              
557             sub plugin_pipe_insert_after {
558 1     1 1 4 my ($self, %params) = @_;
559 1         8 $params{lc $_} = delete $params{$_} for keys %params;
560              
561 1         2 for (qw/after alias plugin/) {
562 3 50       7 confess "Missing required param $_"
563             unless defined $params{$_}
564             }
565              
566 1         3 my ($next_alias, $next_plug)
567             = $self->__plugin_get_plug_any( $params{after} );
568              
569 1 50       36 unless (defined $next_plug) {
570 0         0 $@ = "No such plugin: $next_alias";
571 0         0 carp $@;
572             return
573 0         0 }
574              
575 1 50       2 if ( my $existing = $self->__plugin_by_alias($params{alias}) ) {
576 0         0 $@ = "Already have plugin $params{alias} : $existing";
577 0         0 carp $@;
578             return
579 0         0 }
580              
581 0         0 return unless $self->__plug_pipe_register(
582             $params{alias}, $params{plugin},
583             (
584             ref $params{register_args} eq 'ARRAY' ?
585 1 50       30 @{ $params{register_args} } : ()
    50          
586             ),
587             );
588              
589 1         1 my $idx = 0;
590 1         7 for my $thisplug (@{ $self->__pluggable_pipeline }) {
  1         3  
591 1 50       23 if ($thisplug == $next_plug) {
592 1         2 splice @{ $self->__pluggable_pipeline }, $idx+1, 0, $params{plugin};
  1         3  
593             last
594 1         18 }
595 0         0 $idx++;
596             }
597              
598             1
599 1         4 }
600              
601             sub plugin_pipe_bump_up {
602 1     1 1 448 my ($self, $item, $delta) = @_;
603              
604 1         4 my $idx = $self->plugin_pipe_get_index($item);
605 1 50       3 return -1 unless $idx >= 0;
606              
607 1   50     3 my $pos = $idx - ($delta || 1);
608              
609 1 50       3 unless ($pos >= 0) {
610 0         0 carp "Negative position ($idx - $delta is $pos), bumping to head"
611             }
612              
613 1         3 splice @{ $self->__pluggable_pipeline }, $pos, 0,
  1         21  
614 1         2 splice @{ $self->__pluggable_pipeline }, $idx, 1;
615              
616 1         20 $pos
617             }
618              
619             sub plugin_pipe_bump_down {
620 1     1 1 3 my ($self, $item, $delta) = @_;
621              
622 1         3 my $idx = $self->plugin_pipe_get_index($item);
623 1 50       3 return -1 unless $idx >= 0;
624              
625 1   50     3 my $pos = $idx + ($delta || 1);
626              
627 1 50       2 if ($pos >= @{ $self->__pluggable_pipeline }) {
  1         5  
628 0         0 carp "Cannot bump below end of pipeline, pushing to tail"
629             }
630              
631 1         2 splice @{ $self->__pluggable_pipeline }, $pos, 0,
  1         20  
632 1         25 splice @{ $self->__pluggable_pipeline }, $idx, 1;
633              
634 1         21 $pos
635             }
636              
637             sub __plug_pipe_register {
638 8     8   14 my ($self, $new_alias, $new_plug, @args) = @_;
639              
640             # Register this as a known plugin.
641             # Try to call $reg_prefix . "register"
642              
643 8         8 my ($retval, $err);
644 8         21 my $meth = $self->__pluggable_opts->{reg_prefix} . "register" ;
645              
646             try {
647 8     8   265 $retval = $new_plug->$meth( $self, @args )
648             } catch {
649 0     0   0 chomp;
650 0         0 $err = "$meth call on '$new_alias' failed: $_";
651 8         245 };
652              
653 8 50       125 unless ($retval) {
654 0         0 $err = "$meth call on '$new_alias' returned false";
655             }
656              
657 8 50       14 if ($err) {
658 0         0 $self->__plug_pipe_handle_err( $err, $new_plug, $new_alias );
659             return
660 0         0 }
661              
662 8         19 $self->__pluggable_loaded->{ALIAS}->{$new_alias} = $new_plug;
663 8         188 $self->__pluggable_loaded->{OBJ}->{$new_plug} = $new_alias;
664              
665 8         194 $self->_pluggable_event(
666             $self->__pluggable_opts->{ev_prefix} . "plugin_added",
667             $new_alias,
668             $new_plug
669             );
670              
671 8         33 $retval
672             }
673              
674             sub __plug_pipe_unregister {
675 7     7   14 my ($self, $old_alias, $old_plug, @args) = @_;
676              
677 7         5 my ($retval, $err);
678 7         16 my $meth = $self->__pluggable_opts->{reg_prefix} . "unregister" ;
679              
680             try {
681 7     7   189 $retval = $old_plug->$meth( $self, @args )
682             } catch {
683 0     0   0 chomp;
684 0         0 $err = "$meth call on '$old_alias' failed: $_";
685 7         192 };
686              
687 7 50       124 unless ($retval) {
688 0         0 $err = "$meth called on '$old_alias' returned false";
689             }
690              
691 7 50       51 if ($err) {
692 0         0 $self->__plug_pipe_handle_err( $err, $old_plug, $old_alias );
693             }
694              
695 7         18 delete $self->__pluggable_loaded->{ALIAS}->{$old_alias};
696             delete $self->__pluggable_loaded->{$_}->{$old_plug}
697 7         224 for qw/ OBJ HANDLE /;
698              
699 7         313 $self->_pluggable_event(
700             $self->__pluggable_opts->{ev_prefix} . "plugin_removed",
701             $old_alias,
702             $old_plug
703             );
704              
705 7         23 $retval
706             }
707              
708             sub __plug_pipe_handle_err {
709 0     0   0 my ($self, $err, $plugin, $alias) = @_;
710              
711 0         0 warn "$err\n";
712              
713 0         0 $self->_pluggable_event(
714             $self->__pluggable_opts->{ev_prefix} . "plugin_error",
715             $err,
716             $plugin,
717             $alias
718             );
719             }
720              
721             sub __plugin_by_alias {
722 7     7   13 my ($self, $item) = @_;
723              
724 7         26 $self->__pluggable_loaded->{ALIAS}->{$item}
725             }
726              
727             sub __plugin_by_ref {
728 1     1   2 my ($self, $item) = @_;
729              
730 1         2 $self->__pluggable_loaded->{OBJ}->{$item}
731             }
732              
733             sub __plugin_get_plug_any {
734 19     19   28 my ($self, $item) = @_;
735              
736 19 100       78 blessed $item ?
737             ( $self->__pluggable_loaded->{OBJ}->{$item}, $item )
738             : ( $item, $self->__pluggable_loaded->{ALIAS}->{$item} );
739             }
740              
741              
742             print
743             qq[ How can I run two separate process at the same time simultaneously?\n],
744             qq[ I'd use an operating system, and have it run them for me.\n]
745             unless caller;
746              
747             1;
748              
749             =pod
750              
751             =head1 NAME
752              
753             MooX::Role::Pluggable - Add a plugin pipeline to your cows
754              
755             =head1 SYNOPSIS
756              
757             # A simple pluggable dispatcher:
758             package MyDispatcher;
759             use MooX::Role::Pluggable::Constants;
760             use Moo;
761             with 'MooX::Role::Pluggable';
762              
763             sub BUILD {
764             my ($self) = @_;
765              
766             # (optionally) Configure our plugin pipeline
767             $self->_pluggable_init(
768             reg_prefix => 'Plug_',
769             ev_prefix => 'Event_',
770             types => {
771             NOTIFY => 'N',
772             PROCESS => 'P',
773             },
774             );
775             }
776              
777             around '_pluggable_event' => sub {
778             # This override redirects internal events (errors, etc) to ->process()
779             my ($orig, $self) = splice @_, 0, 2;
780             $self->process( @_ )
781             };
782              
783             sub process {
784             my ($self, $event, @args) = @_;
785              
786             # Dispatch to 'P_' prefixed "PROCESS" type handlers.
787             #
788             # _pluggable_process will automatically strip a leading 'ev_prefix'
789             # (see the call to _pluggable_init above); that lets us easily
790             # dispatch errors to our P_plugin_error handler below without worrying
791             # about our ev_prefix ourselves:
792             my $retval = $self->_pluggable_process( PROCESS =>
793             $event,
794             \@args
795             );
796              
797             unless ($retval == EAT_ALL) {
798             # The pipeline allowed the event to continue.
799             # A dispatcher might re-dispatch elsewhere, etc.
800             }
801             }
802              
803             sub shutdown {
804             my ($self) = @_;
805             # Unregister all of our plugins.
806             $self->_pluggable_destroy;
807             }
808              
809             sub P_plugin_error {
810             # Since we re-dispatched errors in our _pluggable_event handler,
811             # we could handle exceptions here and then eat them, perhaps:
812             my ($self, undef) = splice @_, 0, 2;
813              
814             # Arguments are references:
815             my $plug_err = ${ $_[0] };
816             my $plug_obj = ${ $_[1] };
817             my $error_src = ${ $_[2] };
818              
819             # ...
820              
821             EAT_ALL
822             }
823              
824              
825             # A Plugin object.
826             package MyPlugin;
827              
828             use MooX::Role::Pluggable::Constants;
829              
830             sub new { bless {}, shift }
831              
832             sub Plug_register {
833             my ($self, $core) = @_;
834              
835             # Subscribe to events:
836             $core->subscribe( $self, 'PROCESS',
837             'my_event',
838             'another_event'
839             );
840              
841             # Log that we're here, do some initialization, etc ...
842              
843             return EAT_NONE
844             }
845              
846             sub Plug_unregister {
847             my ($self, $core) = @_;
848             # Called when this plugin is unregistered
849             # ... do some cleanup, etc ...
850             return EAT_NONE
851             }
852              
853             sub P_my_event {
854             # Handle a dispatched "PROCESS"-type event:
855             my ($self, $core) = splice @_, 0, 2;
856              
857             # Arguments are references and can be modified:
858             my $arg = ${ $_[0] };
859              
860             # ... do some work ...
861              
862             # Return an EAT constant to control event lifetime
863             # EAT_NONE allows this event to continue through the pipeline
864             return EAT_NONE
865             }
866              
867             # An external package that interacts with our dispatcher;
868             # this is just a quick and dirty example to show external
869             # plugin manipulation:
870              
871             package MyController;
872             use Moo;
873              
874             has dispatcher => (
875             is => 'rw',
876             default => sub { MyDispatcher->new() },
877             );
878              
879             sub BUILD {
880             my ($self) = @_;
881             $self->dispatcher->plugin_add( 'MyPlugin',
882             MyPlugin->new()
883             );
884             }
885              
886             sub do_stuff {
887             my $self = shift;
888             $self->dispatcher->process( 'my_event', @_ )
889             }
890              
891             =head1 DESCRIPTION
892              
893             A L for turning instances of your class into pluggable objects.
894             Consumers of this role gain a plugin pipeline and methods to manipulate it,
895             as well as a flexible dispatch system (see L).
896              
897             The logic and behavior is based almost entirely on L (see
898             L). Some methods are the same; implementation & some interface
899             differ. Dispatch is significantly faster -- see L.
900              
901             If you're using L, also see L, which consumes
902             this role.
903              
904             =head2 Initialization
905              
906             =head3 _pluggable_init
907              
908             $self->_pluggable_init(
909             # Prefix for registration events.
910             # Defaults to 'plugin_' ('plugin_register' / 'plugin_unregister')
911             reg_prefix => 'plugin_',
912              
913             # Prefix for dispatched internal events
914             # (add, del, error, register, unregister ...)
915             # Defaults to 'plugin_ev_'
916             event_prefix => 'plugin_ev_',
917              
918             # Map type names to prefixes;
919             # Event types can be named arbitrarily. Their respective prefix is
920             # prepended when dispatching events of that type.
921             # Here are the defaults:
922             types => {
923             NOTIFY => 'N',
924             PROCESS => 'P',
925             },
926             );
927              
928             A consumer can call B<_pluggable_init> to set up pipeline-related options
929             appropriately; this should be done prior to loading plugins or dispatching to
930             L. If it is not called, the defaults (as shown above) are
931             used.
932              
933             B<< types => >> can be either an ARRAY of event types (which will be used as
934             prefixes):
935              
936             types => [ qw/ IncomingEvent OutgoingEvent / ],
937              
938             ... or a HASH mapping an event type to a prefix:
939              
940             types => {
941             Incoming => 'I',
942             Outgoing => 'O',
943             },
944              
945             A trailing C<_> is automatically appended to event type prefixes when events
946             are dispatched via L; thus, an event destined for our
947             'Incoming' type shown above will be dispatched to appropriate C handlers:
948              
949             # Dispatched to 'I_foo' method in plugins registered for Incoming 'foo':
950             $self->_pluggable_process( Incoming => 'foo', 'bar', 'baz' );
951              
952             C/C are not automatically munged in any way.
953              
954             An empty string is a valid value for C/C.
955              
956             =head3 _pluggable_destroy
957              
958             $self->_pluggable_destroy;
959              
960             Shuts down the plugin pipeline, unregistering/unloading all known plugins.
961              
962             =head3 _pluggable_event
963              
964             # In our consumer:
965             sub _pluggable_event {
966             my ($self, $event, @args) = @_;
967             # Dispatch out, perhaps.
968             }
969              
970             C<_pluggable_event> is called for internal notifications, such as plugin
971             load/unload and error reporting (see L) -- it should be
972             overriden in your consuming class to do something useful with the dispatched
973             event and any arguments passed.
974              
975             The C<$event> given will be prefixed with the configured B.
976              
977             Also see L.
978              
979             =head2 Registration
980              
981             A plugin is any blessed object that is registered with your Pluggable object
982             via L; during registration, plugins usually subscribe to some
983             events via L.
984              
985             See L regarding loading plugins.
986              
987             =head3 subscribe
988              
989             B
990              
991             $self->subscribe( $plugin_obj, $type, @events );
992              
993             Registers a plugin object to receive C<@events> of type C<$type>.
994              
995             This is typically called from within the plugin's registration handler (see
996             L):
997              
998             # In a plugin:
999             sub plugin_register {
1000             my ($self, $core) = @_;
1001              
1002             $core->subscribe( $self, PROCESS =>
1003             qw/
1004             my_event
1005             another_event
1006             /
1007             );
1008              
1009             $core->subscribe( $self, NOTIFY => 'all' );
1010              
1011             EAT_NONE
1012             }
1013              
1014             Subscribe to B to receive all events -- but note that subscribing many
1015             plugins to 'all' events is less performant during calls to
1016             L than many subscriptions to specific events.
1017              
1018             =head3 unsubscribe
1019              
1020             B
1021              
1022             Carries the same arguments as L.
1023              
1024             The plugin is still loaded and registered until L is called, even
1025             if there are no current event subscriptions.
1026              
1027             =head3 plugin_register
1028              
1029             B
1030              
1031             (Note that 'plugin_' is just a default register method prefix; it can be
1032             changed prior to loading plugins. See L for details.)
1033              
1034             The C method is called on a loaded plugin when it is added to
1035             the pipeline; it is passed the plugin object (C<$self>), the Pluggable object,
1036             and any arguments given to L (or similar registration methods).
1037              
1038             Normally one might call a L from here to start receiving events
1039             after load-time:
1040              
1041             # In a plugin:
1042             sub plugin_register {
1043             my ($self, $core, @args) = @_;
1044             $core->subscribe( $self, 'NOTIFY', @events );
1045             EAT_NONE
1046             }
1047              
1048             =head3 plugin_unregister
1049              
1050             B
1051              
1052             (Note that 'plugin_' is just a default register method prefix; it can be
1053             changed prior to loading plugins. See L for details.)
1054              
1055             The unregister counterpart to L, called when the plugin
1056             object is removed from the pipeline (via L or
1057             L).
1058              
1059             # In a plugin:
1060             sub plugin_unregister {
1061             my ($self, $core) = @_;
1062             EAT_NONE
1063             }
1064              
1065             Carries the same arguments as L.
1066              
1067             =head2 Dispatch
1068              
1069             =head3 _pluggable_process
1070              
1071             # In your consumer's dispatch method:
1072             my $eat = $self->_pluggable_process( $type, $event, \@args );
1073             return 1 if $eat == EAT_ALL;
1074              
1075             The C<_pluggable_process> method handles dispatching.
1076              
1077             If C<$event> is prefixed with our event prefix (see L),
1078             the prefix is stripped prior to dispatch (to be replaced with a type
1079             prefix matching the specified C<$type>).
1080              
1081             Arguments should be passed as a reference to an array. During dispatch,
1082             references to the provided arguments are passed to relevant plugin subroutines
1083             following the automatically-prepended plugin and Pluggable consumer objects
1084             (respectively); this allows for argument modification as an event is passed
1085             along the plugin pipeline:
1086              
1087             my @args = qw/baz bar/;
1088             $self->_pluggable_process( NOTIFY => foo => \@args );
1089              
1090             # In a plugin:
1091             sub N_foo {
1092             # Remove automatically-provided plugin and consumer objects from @_
1093             my ($self, $core) = splice @_, 0, 2;
1094              
1095             # Dereference expected scalars
1096             my $bar = ${ $_[0] };
1097             my $num = ${ $_[1] };
1098              
1099             # Increment actual second argument before pipeline dispatch continues
1100             ++${ $_[1] };
1101              
1102             EAT_NONE
1103             }
1104              
1105             =head4 Dispatch Process
1106              
1107             Your Pluggable consuming class typically provides syntax sugar to
1108             dispatch different types or "classes" of events:
1109              
1110             sub process {
1111             # Dispatch to 'PROCESS'-type events
1112             my ($self, $event, @args) = @_;
1113             my $eat = $self->_pluggable_process( PROCESS => $event, \@args );
1114             # ... possibly take further action based on $eat return value, see below
1115             }
1116              
1117             sub notify {
1118             # Dispatch to 'NOTIFY'-type events
1119             my ($self, $event, @args) = @_;
1120             my $eat = $self->_pluggable_process( NOTIFY => $event, \@args );
1121             # ...
1122             }
1123              
1124             Event types and matching prefixes can be arbitrarily named to provide event
1125             dispatch flexibility. For example, the dispatch process for C<$event> 'foo' of
1126             C<$type> 'NOTIFY' performs the following actions:
1127              
1128             $self->_pluggable_process( NOTIFY => foo => \@args );
1129              
1130             # - Prepend the known prefix for the specified type and '_'
1131             # 'foo' -> 'N_foo'
1132             #
1133             # - Attempt to dispatch to $self->N_foo()
1134             #
1135             # - If no such method, attempt to dispatch to $self->_default()
1136             # (When using _default, the method we were attempting to call is prepended
1137             # to arguments.)
1138             #
1139             # - If the event was not eaten by the Pluggable consumer (see below), call
1140             # $plugin->N_foo() for subscribed plugins sequentially until event is eaten
1141             # or no relevant plugins remain.
1142              
1143             "Eaten" means a handler returned an EAT_* constant from
1144             L indicating that the event's lifetime
1145             should terminate.
1146              
1147             B
1148              
1149             EAT_ALL: skip plugin pipeline, return EAT_ALL
1150             EAT_CLIENT: continue to plugin pipeline
1151             return EAT_ALL if plugin returns EAT_PLUGIN later
1152             EAT_PLUGIN: skip plugin pipeline entirely
1153             return EAT_NONE unless EAT_CLIENT was seen previously
1154             EAT_NONE: continue to plugin pipeline
1155              
1156             B
1157              
1158             EAT_ALL: skip further plugins, return EAT_ALL
1159             EAT_CLIENT: continue to next plugin, set pending EAT_ALL
1160             (EAT_ALL will be returned when plugin processing finishes)
1161             EAT_PLUGIN: return EAT_ALL if previous sub returned EAT_CLIENT
1162             else return EAT_NONE
1163             EAT_NONE: continue to next plugin
1164              
1165             This functionality (derived from L) provides
1166             fine-grained control over event lifetime.
1167              
1168             Higher-level layers (see L for an example) can check
1169             for an C return value from L to determine
1170             whether to continue operating on a particular event (re-dispatch elsewhere,
1171             for example).
1172              
1173             Plugins can use C to indicate that an event should
1174             be eaten after plugin processing is complete, C to stop plugin
1175             processing, and C to indicate that the event should not be dispatched
1176             further.
1177              
1178             =head2 Plugin Management Methods
1179              
1180             These plugin pipeline management methods will set C<$@>, warn via L, and
1181             return an empty list on error (unless otherwise noted). See L
1182             regarding errors raised during plugin registration and dispatch.
1183              
1184             =head3 plugin_add
1185              
1186             $self->plugin_add( $alias, $plugin_obj, @args );
1187              
1188             Add a plugin object to the pipeline.
1189              
1190             Returns the same values as L.
1191              
1192             =head3 plugin_del
1193              
1194             $self->plugin_del( $alias_or_plugin_obj, @args );
1195              
1196             Remove a plugin from the pipeline.
1197              
1198             Takes either a plugin alias or object. Returns the removed plugin object.
1199              
1200             =head3 plugin_get
1201              
1202             my $plug_obj = $self->plugin_get( $alias );
1203             my ($plug_obj, $plug_alias) = $self->plugin_get( $alias_or_plugin_obj );
1204              
1205             In scalar context, returns the plugin object belonging to the specified
1206             alias.
1207              
1208             In list context, returns the object and alias, respectively.
1209              
1210             =head3 plugin_alias_list
1211              
1212             my @loaded = $self->plugin_alias_list;
1213              
1214             Returns the list of loaded plugin aliases.
1215              
1216             As of version C<1.002>, the list is ordered to match actual plugin dispatch
1217             order. In prior versions, the list is unordered.
1218              
1219             =head3 plugin_replace
1220              
1221             $self->plugin_replace(
1222             old => $alias_or_plugin_obj,
1223             alias => $new_alias,
1224             plugin => $new_plugin_obj,
1225             # Optional:
1226             register_args => [ ],
1227             unregister_args => [ ],
1228             );
1229              
1230             Replace an existing plugin object with a new one.
1231              
1232             Returns the old (removed) plugin object.
1233              
1234             =head2 Pipeline Methods
1235              
1236             =head3 plugin_pipe_push
1237              
1238             $self->plugin_pipe_push( $alias, $plugin_obj, @args );
1239              
1240             Add a plugin to the end of the pipeline; typically one would call
1241             L rather than using this method directly.
1242              
1243             =head3 plugin_pipe_pop
1244              
1245             my $plug = $self->plugin_pipe_pop( @unregister_args );
1246              
1247             Pop the last plugin off the pipeline, passing any specified arguments to
1248             L.
1249              
1250             In scalar context, returns the plugin object that was removed.
1251              
1252             In list context, returns the plugin object and alias, respectively.
1253              
1254             =head3 plugin_pipe_unshift
1255              
1256             $self->plugin_pipe_unshift( $alias, $plugin_obj, @args );
1257              
1258             Add a plugin to the beginning of the pipeline.
1259              
1260             Returns the total number of loaded plugins (or an empty list on failure).
1261              
1262             =head3 plugin_pipe_shift
1263              
1264             $self->plugin_pipe_shift( @unregister_args );
1265              
1266             Shift the first plugin off the pipeline, passing any specified args to
1267             L.
1268              
1269             In scalar context, returns the plugin object that was removed.
1270              
1271             In list context, returns the plugin object and alias, respectively.
1272              
1273             =head3 plugin_pipe_get_index
1274              
1275             my $idx = $self->plugin_pipe_get_index( $alias_or_plugin_obj );
1276             if ($idx < 0) {
1277             # Plugin doesn't exist
1278             }
1279              
1280             Returns the position of the specified plugin in the pipeline.
1281              
1282             Returns -1 if the plugin does not exist.
1283              
1284             =head3 plugin_pipe_insert_after
1285              
1286             $self->plugin_pipe_insert_after(
1287             after => $alias_or_plugin_obj,
1288             alias => $new_alias,
1289             plugin => $new_plugin_obj,
1290             # Optional:
1291             register_args => [ ],
1292             );
1293              
1294             Add a plugin to the pipeline after the specified previously-existing alias
1295             or plugin object. Returns boolean true on success.
1296              
1297             =head3 plugin_pipe_insert_before
1298              
1299             $self->plugin_pipe_insert_before(
1300             before => $alias_or_plugin_obj,
1301             alias => $new_alias,
1302             plugin => $new_plugin_obj,
1303             # Optional:
1304             register_args => [ ],
1305             );
1306              
1307             Similar to L, but insert before the specified
1308             previously-existing plugin, not after.
1309              
1310             =head3 plugin_pipe_bump_up
1311              
1312             $self->plugin_pipe_bump_up( $alias_or_plugin_obj, $count );
1313              
1314             Move the specified plugin 'up' C<$count> positions in the pipeline.
1315              
1316             Returns -1 if the plugin cannot be bumped up any farther.
1317              
1318             =head3 plugin_pipe_bump_down
1319              
1320             $self->plugin_pipe_bump_down( $alias_or_plugin_obj, $count );
1321              
1322             Move the specified plugin 'down' C<$count> positions in the pipeline.
1323              
1324             Returns -1 if the plugin cannot be bumped down any farther.
1325              
1326             =head2 Internal Events
1327              
1328             These events are dispatched to L prefixed with our
1329             pluggable event prefix; see L.
1330              
1331             =head3 plugin_error
1332              
1333             Issued via L when an error occurs.
1334              
1335             The arguments are, respectively: the error string, the offending object,
1336             and a string describing the offending object ('self' or 'plugin' with name
1337             appended).
1338              
1339             =head3 plugin_added
1340              
1341             Issued via L when a new plugin is registered.
1342              
1343             Arguments are the new plugin alias and object, respectively.
1344              
1345             =head3 plugin_removed
1346              
1347             Issued via L when a plugin is unregistered.
1348              
1349             Arguments are the old plugin alias and object, respectively.
1350              
1351             =head2 Performance
1352              
1353             My motivation for writing this role was two-fold; I wanted
1354             L behavior but without screwing up my class inheritance,
1355             and I needed a little bit more juice out of the pipeline dispatch process for
1356             a fast-paced daemon.
1357              
1358             Dispatcher performance has been profiled and micro-optimized, but I'm most
1359             certainly open to further ideas ;-)
1360              
1361             Some L runs. 30000 L calls with 20 loaded
1362             plugins dispatching one argument to one handler that does nothing except
1363             return EAT_NONE:
1364              
1365             Rate object-pluggable moox-role-pluggable
1366             object-pluggable 6173/s -- -38%
1367             moox-role-pluggable 9967/s 61%
1368              
1369             Rate object-pluggable moox-role-pluggable
1370             object-pluggable 6224/s -- -38%
1371             moox-role-pluggable 10000/s 61% --
1372              
1373             Rate object-pluggable moox-role-pluggable
1374             object-pluggable 6383/s -- -35%
1375             moox-role-pluggable 9868/s 55%
1376              
1377             (Benchmark script is available in the C directory of the upstream
1378             repository; see L)
1379              
1380             =head1 AUTHOR
1381              
1382             Jon Portnoy
1383              
1384             Written from the ground up, but conceptually derived entirely from
1385             L (c) Chris Williams, Apocalypse, Hinrik Orn Sigurosson and
1386             Jeff Pinyan.
1387              
1388             Licensed under the same terms as Perl 5; please see the license that came with
1389             your Perl distribution for details.
1390              
1391             =cut