File Coverage

blib/lib/Module/Modular.pm
Criterion Covered Total %
statement 24 81 29.6
branch 1 22 4.5
condition 0 5 0.0
subroutine 7 14 50.0
pod 3 3 100.0
total 35 125 28.0


line stmt bran cond sub pod time code
1             package Module::Modular;
2              
3             =head1 NAME
4              
5             Module::Modular - Create optional plugins for your module
6              
7             =head1 DESCRIPTION
8              
9             Module::Modular allows you, or others, to create plugins for your modules. They are not loaded by default - only
10             when you want them to be. This means you can even load them further on down in your module if you wish.
11             The idea is to have your plugins handle certain tasks you don't want cluttering the core code of your module.
12             I started writing this B I came across another plugin module called L. So if you like
13             how that one works better, or even prefer the name (I do), then go check it out. This one is different in the sense
14             you explicitly tell your module what plugins to load, and each plugin may have an initialiser (C<__init>) that will
15             get run once it has been loaded, which I found pretty neat.
16             This module is modular itself. By importing C followed by an array of options you can extend the functionality
17             of Module::Modular. Currently just the one option is available (C) which provides methods for accessing meta data of your plugins.
18             A plugin can only be loaded if it's within the same namespace and within your path (ie: YourModule::Plugin::*)
19              
20             =head1 SYNOPSIS
21              
22             # MyModule.pm
23            
24             package MyModule;
25              
26             use Module::Modular;
27             load_plugins qw;
28            
29             sub load_another_plugin {
30             load_plugins 'DifferentOne';
31             }
32              
33             # MyModule::Plugin::Foo
34             package MyModule::Plugin::Foo;
35              
36             sub __init {
37             my ($class, $name) = @_;
38             # $class = MyModule::Plugin::Foo
39             # $name = Foo
40              
41             # some code here to be run when loaded
42             }
43              
44             sub foo {
45             print "You have been foo'd!\n";
46             }
47              
48             Now, when you C, the Foo plugin will get loaded and run C<__init> from C. Simple. The initialiser is completely optional.
49             It's quite simple to get a list of plugins, or you can get hold of a single plugin to do stuff with.
50              
51             # Run the foo() method within the Foo plugin
52             my $foo_plugin = $module->plugin('Foo')->foo();
53              
54             Calling the C method will return an array of your loaded plugins. Each one will be blessed, so you have objects to work with which makes things easier.
55              
56             # call the foo() method on every loaded plugin
57             for my $plugin ($module->plugins) {
58             $plugin->foo();
59             }
60              
61             =head1 METHODS
62              
63             C exports only a few functions into your module. They are...
64              
65             =head2 load_plugins
66              
67             void load_plugins(@list)
68              
69             Takes an array of plugins (Not their entire path, just the name of the plugin. For example,
70             if I wanted to load C I would only have to use C.
71             If it can't load the module for any reason it will print out a warnings and move onto the next one if it's specified.
72              
73             =head2 plugins
74              
75             @array plugins(void)
76              
77             Returns an array of your loaded plugins. It will only register those introduced by C, just having one in the right namespace and loaded by any other means will do nothing.
78              
79             =head2 plugin
80              
81             $object plugin(string)
82              
83             Returns a blessed reference of a plugin (ie: The plugin object). You only need to supply the name, not the entire path. For example
84              
85             my $plugin = $module->plugin('Foo');
86              
87             =head2 stash
88              
89             This is a plugin method (Called from a plugin only). It's a really simple method used to get the data of a specific global variable from the base module. After all, what's the point of a plugin if you have absolutely no way to share data, right?
90              
91             # MyModule.pm
92             package MyModule;
93              
94             use Module::Modular;
95              
96             our $PluginStash = { bees => 'knees' };
97             load_plugins 'Bees';
98              
99             # MyModule::Plugin::Bees
100             package MyModule::Plugin::Bees;
101              
102             sub __init {
103             my ($self, $name) = @_;
104             print "The $name have " . $self->stash('bees');
105             }
106              
107             It's not overly useful, secure, or remotely interesting, but if you need a quick and dirty way to get some data to your plugins, it works. I am trying to find a neater solution..
108              
109             =head2 OPTIONS
110              
111             When you C you can pass a key called C as an arrayref of options or just a string for a single option.
112              
113             B
114              
115             Implements specific accessors to access the meta data of a plugin
116              
117             # MyModule.pm
118             use Module::Modular
119             with => 'Accessors';
120              
121             load_plugins qw;
122              
123             # test.pl
124             for my $plugin ($module->plugins) {
125             say "Name: " . $plugin->name;
126             say "Version: " . $plugin->version;
127             }
128              
129             B
130              
131             Will not allow you to get a plugin object directly from outside the core modules scope
132              
133             # MyModule.pm
134             use Module::Modular
135             with => [qw];
136              
137             load_plugins 'Foo';
138             sub get_plugin { my $self = shift; return $self->plugin('Foo'); }
139              
140             # test.pl
141             my $c = MyModule->new;
142             $c->plugin('Foo')->foo(); # fails
143             my $plugin = $c->get_plugin(); # works, because it went through the core module first
144            
145             =cut
146              
147 1     1   21654 use warnings;
  1         4  
  1         32  
148 1     1   6 use strict;
  1         2  
  1         41  
149              
150 1     1   874 use Import::Into;
  1         2897  
  1         242  
151              
152             our $VERSION = '0.003';
153             our $LoadedPlugins = [];
154             our $Config = {
155             'accessors' => 0,
156             'strict' => 0,
157             };
158              
159             sub import {
160 1     1   10 my ($class, %opts) = @_;
161 1         3 my $caller = scalar caller;
162              
163 1 50       4 if (exists $opts{with}) {
164 0 0       0 if (ref($opts{with})) {
165 0         0 for my $with (@{$opts{with}}) {
  0         0  
166 0         0 _enable_option($with);
167             }
168             }
169             else {
170 0         0 _enable_option($opts{with});
171             }
172             }
173              
174 1         3 _import_defs($caller,
175             qw);
176             }
177              
178             sub _enable_option {
179 0     0   0 my $opt = shift;
180 0         0 for ($opt) {
181 0 0       0 if (/Accessors/) { $Config->{accessors} = 1; }
  0 0       0  
182 0         0 elsif (/Strict/) { $Config->{strict} = 1; }
183             }
184             }
185              
186             sub _import_defs {
187 1     1   3 my ($caller, @methods) = @_;
188 1         101 importmethods: {
189 1     1   5 no strict 'refs';
  1         2  
  1         2  
190 1         2 foreach my $method (@methods) {
191 3         5 *{"${caller}::${method}"} = \&$method;
  3         24  
192             }
193             }
194             }
195              
196             sub load_plugins {
197 0     0 1   my (@plugins) = @_;
198 0           my $caller = caller;
199 0           my $name;
200 1         525 loadplugins: {
201 1     1   4 no strict 'refs';
  1         7  
  0            
202 0           foreach my $plugin (@plugins) {
203 0           $name = $plugin;
204 0           $plugin = "${caller}::Plugin::${plugin}";
205 0           eval "use $plugin;";
206 0 0         if ($@) {
207 0           warn "Failed loading plugin ${plugin}: ${@}";
208 0           next;
209             }
210              
211 0           $plugin->import::into($caller);
212 0           *{"${plugin}::stash"} = sub {
213 0     0     my ($mod, $key) = @_;
214 0 0         if ($mod = ref($mod)) {
215 0           my $module = (split('::', $mod))[0];
216 0           return ${"${module}::PluginStash"}->{$key};
  0            
217             }
218 0           };
219              
220 0 0         if ($plugin->can('__init')) {
221 0           my $blessed_plugin = bless {}, $plugin;
222 0           $blessed_plugin->__init($name);
223             }
224 0   0       push @$LoadedPlugins, bless {
225             name => $name,
226             version => $plugin->VERSION||'Unknown',
227             }, $plugin;
228            
229 0 0         if ($Config->{accessors}) {
230 0     0     *{"${plugin}::name"} = sub { return shift->{name}; };
  0            
  0            
231 0     0     *{"${plugin}::version"} = sub { return shift->{version}; };
  0            
  0            
232             }
233             }
234             }
235             }
236              
237             sub plugin {
238 0     0 1   my ($self, $plugin) = @_;
239 0           my $caller = caller();
240 0           my @plugins = $self->plugins;
241 0 0         if (my $first = $plugins[0]) {
242 0           $plugin = ref($self) . "::Plugin::" . $plugin;
243 0 0 0       if ($Config->{strict} and ref($first) ne "${caller}::Plugin::" . $first->{name}) {
244 0           warn "Can't call plugin outside core modules scope";
245 0           return 0;
246             }
247 0 0         if (grep { ref($_) eq $plugin } @$LoadedPlugins) {
  0            
248 0           return bless {}, $plugin;
249             }
250             else {
251 0           warn "Could not get plugin ${plugin}: Not loaded";
252 0           return 0;
253             }
254             }
255             else {
256 0           warn "No plugins loaded";
257 0           return 0;
258             }
259             }
260              
261             sub plugins {
262 0     0 1   my $self = shift;
263 0           return @$LoadedPlugins;
264             }
265              
266             =head1 AUTHOR
267              
268             Brad Haywood
269              
270             =head1 LICENSE
271              
272             This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.
273              
274             =cut
275              
276             1;