File Coverage

blib/lib/MooX/Role/Pluggable.pm
Criterion Covered Total %
statement 239 325 73.5
branch 89 164 54.2
condition 14 29 48.2
subroutine 36 42 85.7
pod 16 16 100.0
total 394 576 68.4


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