File Coverage

blib/lib/MooX/Object/Pluggable.pm
Criterion Covered Total %
statement 87 91 95.6
branch 24 26 92.3
condition 3 3 100.0
subroutine 15 17 88.2
pod 3 5 60.0
total 132 142 92.9


line stmt bran cond sub pod time code
1             package MooX::Object::Pluggable;
2 4     4   98643 use Moo::Role;
  4         28887  
  4         30  
3 4     4   1357 use Modern::Perl;
  4         9  
  4         34  
4 4     4   681 use Scalar::Util 'refaddr';
  4         7  
  4         418  
5             require Module::Pluggable::Object;
6 4     4   1916 use namespace::clean;
  4         35796  
  4         27  
7              
8             our $VERSION = '0.0.5'; # VERSION
9             # ABSTRACT: Moo eXtension to inject plugins to exist objects as a role
10              
11              
12             sub _apply_roles {
13 16     16   27 my ($self, @roles) = @_;
14             map {
15 16         19 my $role = $_;
  21         178  
16 21 100       65 Moo::Role->apply_roles_to_object($self, $role) unless $self->does($role)
17             } @roles;
18 16         5326 return $self;
19             }
20              
21 0     0 0 0 sub load_plugin { load_plugins(@_) }
22              
23             sub load_plugins {
24 15     15 1 433 my ($self, @plugin_options) = @_;
25 15         31 my $pluggable_object = $self->_pluggable_object;
26 15         68 my @plugins = $pluggable_object->plugins;
27             # Provide ability for roles in a real package, with syntax: '+MooX::ConfigFromFile'
28             map {
29 1         2 my $option = $_; $option=~s/^\+//;
  1         3  
30 1         3 $self->_apply_roles($option);
31 15         8700 } grep { /^\+/ } @plugin_options;
  17         45  
32 15 50       40 return $self unless @plugins;
33 15         52 for my $plugin_option (@plugin_options) {
34 17 100       67 if ($plugin_option eq '-all') {
    100          
    100          
35 1         3 $self->_apply_roles(@plugins);
36             } elsif (ref $plugin_option eq 'ARRAY') {
37 1         6 $self->load_plugins(@$plugin_option);
38             } elsif (ref $plugin_option eq 'Regexp') {
39 4         5 my @load_plugins = grep { $plugin_option } @plugins;
  8         11  
40 4 50       12 return $self unless @load_plugins;
41 4         10 $self->_apply_roles(@load_plugins);
42             } else {
43 11         12 my @load_plugins = map { $_.'::'.$plugin_option } @{$pluggable_object->{search_path}};
  11         33  
  11         44  
44 11         20 my %all_plugins = map { $_ => 1 } @plugins;
  17         36  
45 11         15 my @real_roles = grep { $all_plugins{$_} } @load_plugins;
  11         27  
46 11 100       28 return $self unless @real_roles;
47 10         30 $self->_apply_roles(@real_roles)
48             }
49             }
50 14         121 return $self;
51             }
52              
53              
54             sub plugins {
55 1     1 1 2 my ($self) = @_;
56 1         5 $self->_pluggable_object->plugins;
57             }
58              
59              
60             sub loaded_plugins {
61 0     0 1 0 my $self = shift;
62 0         0 grep { $self->does($_) } $self->plugins;
  0         0  
63             }
64              
65              
66             my %pluggable_objects = (); # key: object, value: loaded plugins
67              
68       13 0   sub BUILD { } # BUILD() will be override by consumers, so we use afterBuild
69              
70             after BUILD => sub {
71             my ($self, $opts) = @_;
72             if (defined $opts->{pluggable_options}) {
73             my $pluggable_options = $opts->{pluggable_options};
74             $pluggable_options->{package} = ref $self ? ref $self : $self;
75             $pluggable_objects{refaddr($self)} = Module::Pluggable::Object->new(%$pluggable_options);
76             }
77             if (defined $self->_build_load_plugins and scalar @{$self->_build_load_plugins} > 0) {
78             $self->load_plugins(@{$self->_build_load_plugins});
79             }
80             if (defined $opts->{load_plugins}) {
81             $self->load_plugins(ref $opts->{load_plugins} eq 'ARRAY' ?
82             @{$opts->{load_plugins}} : $opts->{load_plugins}
83             );
84             }
85             };
86              
87 2     2   5 sub _build_pluggable_options { {} }
88              
89 18     18   60 sub _build_load_plugins { [] }
90              
91             sub _pluggable_object {
92 28     28   13561 my $self = shift;
93 28         31 my ($class, $addr);
94 28 100       53 if (ref $self) {
95 23         27 $class = ref $self;
96 23         62 $addr = refaddr $self;
97             } else {
98 5         9 $class = $self;
99             }
100             # Find self pluggable object;
101 28 100 100     157 return $pluggable_objects{$addr} if defined $addr and defined $pluggable_objects{$addr};
102             # Find package pluggable object;
103 25         54 $class=~s/__WITH__.*//g; # use parent package name as class name.
104 25 100       86 return $pluggable_objects{$class} if defined $pluggable_objects{$class};
105             # Not found, create a new one for package.
106 5         70 my $pluggable_options = $self->_build_pluggable_options;
107 5         12 $pluggable_options->{package} = $class;
108 5         42 $pluggable_objects{$class} = Module::Pluggable::Object->new(
109             %$pluggable_options,
110             );
111             }
112              
113              
114             sub _inject_roles_to {
115 8     8   16 my ($target, $import_options) = @_;
116 8         100 my $with = $target->can("with");
117 8 100       33 return unless $with; # Do nothing unless it's a Moo(se) object or role.
118              
119 5         20 $with->('MooX::Object::Pluggable');
120 5         30777 my $around = $target->can("around");
121 5         13 for my $builder (qw/pluggable_options load_plugins/) {
122 10         1233 my ($key) = grep /$builder/, keys %$import_options;
123 10 100       53 next unless $key;
124 7     18   45 $around->("_build_$builder" => sub { $import_options->{$key} });
  18         414  
125             }
126             }
127              
128             sub import
129             {
130 7     7   9725 my ( undef, %import_options ) = @_;
131 7         20 my $target = caller;
132             # Inject roles to target namespace
133 7         22 &_inject_roles_to($target, \%import_options);
134              
135             # Compatible for MooX
136 7         903 my $around = $target->can("around");
137 7 100       1721 return unless $around;
138             $around->("import" => sub {
139 1     1   59 my ($orig, $self, @opts) = @_;
140 1         4 my %pluggable_opts = map { $opts[$_] => $opts[$_ + 1] } grep { $opts[$_] =~/^-(pluggable_options|load_plugins)$/ } 0..$#opts;
  1         5  
  2         10  
141 1         4 &_inject_roles_to($target, \%pluggable_opts);
142 1         264 my %hash = map { $_ => 1 } %pluggable_opts;
  2         6  
143 1         2 my @remains = grep { ! defined $hash{$_} } @opts;
  2         6  
144 1         5 $self->$orig(@remains);
145 4         28 });
146 4         1285 return;
147             }
148              
149              
150             1;
151              
152             __END__