File Coverage

blib/lib/Object/Meta/Plugin/Host.pm
Criterion Covered Total %
statement 134 156 85.9
branch 28 44 63.6
condition 16 32 50.0
subroutine 31 35 88.5
pod 9 10 90.0
total 218 277 78.7


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             # $Id: Host.pm,v 1.12 2003/11/29 14:35:24 nothingmuch Exp $
3              
4             package Object::Meta::Plugin::Host;
5              
6 3     3   2105 use strict;
  3         5  
  3         87  
7 3     3   13 use warnings;
  3         5  
  3         75  
8              
9 3     3   13 use autouse Carp => qw(croak);
  3         5  
  3         12  
10 3     3   2481 use Tie::RefHash;
  3         23590  
  3         3403  
11              
12             our $VERSION = 0.01;
13             our $AUTOLOAD;
14              
15             sub new {
16 16     16 0 3064 my $pkg = shift;
17 16         63 my $self = {
18             plugins => {}, # plugin ref hash
19             methods => {}, # method look up, with an array of plugin refs per method
20             };
21            
22 16         24 tie %{ $self->{plugins} }, 'Tie::RefHash';
  16         93  
23            
24 16         182 bless $self, $pkg;
25             }
26              
27             sub self { # used when we're not plugged
28 599     599 1 899 return $_[0];
29             }
30              
31             sub plugins {
32 88     88 1 407 my $self = shift->self; # in case we're plugged
33 88         307 return $self->{plugins};
34             }
35              
36             sub methods {
37 210     210 1 333 my $self = shift->self; # in case we're plugged
38 210         1004 return $self->{methods};
39             }
40              
41             sub plug {
42 34     34 1 288 my $self = shift->self; # in case we're plugged
43 34         45 my $plugin = shift;
44            
45 34 100       43 croak "Doesn't look like a plugin" if (grep { not $plugin->can($_) } qw/init/);
  34         334  
46              
47 33   66     112 return $self->register($plugin->init(@_) or croak "init() did not return an export list");
48             }
49              
50             sub unplug { #
51 1     1 1 69 my $self = shift->self; # in case we're plugged
52              
53 1         3 foreach my $plugin (@_){
54 4         29 foreach my $method (keys %{ $self->methods }){
  4         8  
55 16 100       91 next unless $plugin->can($method);
56 10         11 @{ $self->methods->{$method} } = grep { $_ != $plugin } @{ $self->methods->{$method} };
  10         19  
  35         70  
  10         27  
57 10 50       24 delete $self->methods->{$method} unless @{ $self->methods->{$method} };
  10         19  
58             }
59            
60 4         11 delete $self->plugins->{$plugin};
61             }
62             # munge the stack
63             }
64              
65             sub register { # export list
66 32     32 1 81 my $self = shift->self; # in case we're plugged
67 32         40 my $x = shift;
68            
69             # create the stack
70            
71 32 100 66     89 croak "That doesn't look like a valid export list" if (!$x or grep { not $x->can($_) } qw/list plugin exists merge unmerge/);
  160         1858  
72            
73 31         90 foreach my $method ($x->list()){
74 73 100       607 croak "Method $method is reserved for use by the context object" if Object::Meta::Plugin::Host::Context->UNIVERSAL::can($method);
75 72 100       193 croak "Can't locate object method \"$method\" via package \"", (ref $x->plugin()), "\"" unless $x->plugin->can($method);
76            
77 71   100     146 my $stack = $self->stack($method) || [];
78            
79 71         89 push @{$stack}, $x->plugin();
  71         199  
80            
81 71         144 $self->stack($method, $stack);
82             }
83              
84 29 100       73 exists $self->plugins->{$x->plugin} ? $self->plugins->{$x->plugin}->merge($x) : $self->plugins->{$x->plugin} = $x; # should return success
85             }
86              
87             sub unregister {
88 0     0 1 0 my $self = shift->self;
89            
90 0         0 foreach my $x (@_){
91 0 0 0     0 croak "That doesn't look like a valid export list" if (!$x or grep { not $x->can($_) } qw/list plugin/);
  0         0  
92            
93 0         0 $self->plugins->{$x->plugin}->unmerge($x);
94            
95 0 0       0 if ($x->list()){
96 0         0 foreach my $method ($x->list()){
97 0 0       0 next unless $self->stack($method);
98            
99 0         0 @{ $self->stack($method) } = grep { $_ != $x->plugin } @{ $self->stack($method) };
  0         0  
  0         0  
  0         0  
100            
101 0 0       0 delete $self->methods->{$method} unless (@{ $self->stack($method) });
  0         0  
102             }
103             } else {
104 0         0 $self->unplug($x->plugin());
105             }
106             }
107             }
108              
109             sub stack { # : lvalue { # grant access to the stack of a certain method.
110 176     176 1 291 my $self = shift->self; # in case we're plugged
111 176         216 my $method = shift;
112            
113 176 100       417 @_ ? ($self->methods->{$method} = shift) : $self->methods->{$method};
114             }
115              
116             sub can { # provide a subref you can goto
117 7     7 1 186 my $self = shift->self; # in case we're plugged
118 7         9 my $method = shift;
119 7   50 0   45 return $self->UNIVERSAL::can($method) || ($self->stack($method) && sub { $AUTOLOAD = "::" . $method; goto &AUTOLOAD }) || undef;
  0         0  
  0         0  
120             }
121              
122             sub AUTOLOAD { # where the magic happens
123 19     19   431 my $super = shift;
124 19         49 my $self = $super->self; # in case we're plugged
125              
126 19         94 $AUTOLOAD =~ /.*::(.*?)$/;
127 19         39 my $method = $1;
128 19 100       190 croak "Method $method is reserved for use by the context object" if Object::Meta::Plugin::Host::Context->UNIVERSAL::can($method);
129            
130 18 50       37 return undef if $method eq 'DESTROY';
131 18 100       40 my $stack = $self->stack($method) or croak "Can't locate object method \"$method\" via any plugin in $self";
132 17         25 Object::Meta::Plugin::Host::Context->new($super, ${ $stack }[ $#$stack ])->$method(@_); # __PACKAGE__::Context ?
  17         51  
133             }
134              
135             package Object::Meta::Plugin::Host::Context; # the wrapper object which defines the context of a plugin
136              
137 3     3   28 use strict;
  3         6  
  3         114  
138 3     3   14 use warnings;
  3         34  
  3         97  
139              
140 3     3   14 use autouse Carp => qw(croak);
  3         13  
  3         18  
141              
142             our $VERSION = 0.01;
143             our $AUTOLOAD;
144              
145             sub new {
146 22     22   26 my $pkg = shift;
147            
148 22   50     152 my $self = bless {
149             host => shift,
150             plugin => shift,
151             instance => shift || 0, # a plugin can be plugged into several slots, each of which needs it's own context
152             }, $pkg;
153            
154 22         487 $self;
155             }
156              
157             sub offset { # get a context with a numerical offset
158 7     7   8 my $self = shift;
159 7         8 my $offset = shift;
160            
161 7         16 Object::Meta::Plugin::Host::Context::Offset->new($self->host,$self->plugin,$offset,$self->{instance});
162             }
163              
164             sub prev { # an overlying method - call a context one above
165 3     3   52 my $self = shift;
166 3         9 $self->offset(1);
167             }
168              
169             sub next { # an underlying method - call a context one below
170 4     4   65 my $self = shift;
171 4         9 $self->offset(-1);
172             }
173              
174             sub super { # the real self: Object::Meta::Plugin::Host
175 40     40   199 my $self = shift;
176 40         132 $self->{host};
177             }
178 7     7   14 sub host { goto &super }
179              
180             sub plugin {
181 53     53   59 my $self = shift;
182 53         381 $self->{plugin};
183             }
184 0     0   0 sub self { goto &plugin }
185              
186             sub can { # try to return the correct method.
187 0     0   0 my $self = shift;
188 0         0 my $method = shift;
189 0 0 0     0 $self->UNIVERSAL::can($method) || $self->plugin->can($method) || $self->host->can($method); # it's one of these, in that order
190             }
191              
192             sub AUTOLOAD {
193 23     23   52 my $self = shift;
194            
195 23         82 $AUTOLOAD =~ /.*::(.*?)$/;
196 23         42 my $method = $1;
197 23 50       46 return undef if $method eq 'DESTROY';
198            
199 23 50       24 if (${ $self->super->self->plugins }{$self->plugin}->exists($method)){ # examine the plugin's export list in the host
  23         44  
200             ### stray from magic - this is as worse as it should get
201 23         38 unshift @_, $self; # return self to the argument list. Should be O(1). lets hope.
202 23 50       21 goto &{ $self->plugin->can($method) or croak ($self->plugin . " can't $method") };
  23         42  
203             } else {
204 0         0 $self->host->$method(@_);
205             }
206             }
207              
208             package Object::Meta::Plugin::Host::Context::Offset; # used to implement next and previous.
209              
210 3     3   1727 use strict;
  3         6  
  3         85  
211 3     3   13 use warnings;
  3         5  
  3         93  
212 3     3   14 use autouse Carp => qw(croak);
  3         14  
  3         22  
213              
214             our $AUTOLOAD;
215              
216             sub new {
217 7     7   11 my $pkg = shift;
218            
219 7   50     59 my $self = bless {
220             host => shift,
221             plugin => shift,
222             offset => shift,
223             instance => shift || 0,
224             }, $pkg;
225            
226 7         73 $self;
227             }
228              
229 2     2   7 sub can { $AUTOLOAD = ref $_[0] . "::can"; goto &AUTOLOAD; }; # $$$ not yet tested. I'm pretty sure AUTOLOAD will [not][ be hit after UNIVERSAL::can is found. It doesn't rally matter.
  2         10  
230             sub AUTOLOAD { # it has to be less ugly than this
231 9     9   15 my $self = shift;
232 9         29 $AUTOLOAD =~ /.*::(.*?)$/;
233 9         24 my $method = $1;
234 9 50       22 return undef if $method eq 'DESTROY';
235              
236 9   33     25 my $stack = $self->{host}->self->stack($method) || croak "Can't locate object method \"$method\" via any plugin in ${ $self }{host}";
237            
238 9         13 my %counts;
239              
240             my $i;
241 9         11 my $j = $self->{instance};
242            
243 9   33     33 for ($i = $#$stack; $i >= 0 or croak "The plugin which requested an offset is not in the stack for the method $method which it called"; $i--){
244 13 100 66     12 ${ $stack }[$i] == $self->{plugin} and (-1 == --$j) and last;
  13         75  
245 4         7 $counts{ ${ $stack }[$i] }++;
  4         19  
246             }
247            
248 9         14 my $x = $i;
249 9         86 $i += $self->{offset};
250 9         25 for (; $x >= $i; $x--){
251 10         12 $counts{ ${ $stack }[$x] }++;
  10         44  
252             }
253              
254 9 100 100     812 croak "The offset is out of the range of the method stack for $method\n" if ($i > $#$stack or $i < 0);
255            
256 5         10 Object::Meta::Plugin::Host::Context->new($self->{host}, ${ $stack }[$i], $counts{${ $stack }[$i]} -1 )->$method(@_);
  5         8  
  5         21  
257             }
258              
259             1; # Keep your mother happy.
260              
261             __END__