File Coverage

blib/lib/Class/Mutator.pm
Criterion Covered Total %
statement 50 52 96.1
branch 11 16 68.7
condition 2 6 33.3
subroutine 10 10 100.0
pod 6 6 100.0
total 79 90 87.7


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             =head1 NAME
4              
5             Class::Mutator - Run-time Dynamic Multiple Inheritance
6              
7             =head1 SYNOPSIS
8              
9             package Frog;
10             use Class::Mutator qw( -isasubclass );
11             sub new { ... }
12             sub eat_flies { ... }
13              
14             package Prince;
15             sub be_charming { ... }
16              
17             my $froggie = Frog->new;
18             $froggie->mutate('Prince');
19              
20             # Now the froggie has been reblessed into a "Frog Prince"
21             # class and can take advantage of methods in both classes.
22             $froggie->eat_flies;
23             $froggie->be_charming;
24              
25             =head1 DESCRIPTION
26              
27             Class::Mutator adds the power of "dynamic polymorphism" to Perl
28             objects.
29              
30             Any object that inherits Class::Mutator principally gains two new
31             methods, mutate and unmutate that allows them to add methods to
32             themselves at runtime from other packages. The most recently mutated
33             packages take precedence when methods with the same name are defined
34             in more than one package.
35              
36             This module is similar to Sex.pm, which Michael Schwern was working
37             on around the same time, only a little bit more predictable. It
38             came about while I was doing some training at the BBC and someone
39             asked how you could do this easily; after discussion with my fellow
40             London.pm'ers, in particular Piers Cawley, this module came about.
41              
42             More recently Matthew Simon Cavalletto sent me a version with
43             everything I had been meaning to do on the module a little after I
44             uploaded version 0.03 which only had more substantial tests. So major
45             kudos to Matthew.
46              
47             =head1 USE
48              
49             To enable a class of objects to mutate, make it a subclass of
50             Class::Mutator.
51              
52             package MyBaseClass;
53             use Class::Mutator;
54             push @ISA, 'Class::Mutator';
55             ...
56             MyBaseClass->new()->mutate( ... );
57              
58             As a shortcut, you may pass the C<-isasubclass> flag in your use
59             statement, which will produce the same result.
60              
61             package MyBaseClass;
62             use Class::Mutator '-isasubclass';
63             ...
64             MyBaseClass->new()->mutate( ... );
65              
66             Finally, if you need to retroactively add mutation capabilities to
67             an existing class, you can do so using the same syntax, with the
68             target class passeds as a parameter.
69              
70             package main;
71             use MyBaseClass;
72             use Class::Mutator '-isasubclass' => 'MyBaseClass';
73             MyBaseClass->new()->mutate( ... );
74              
75             You can also import the specific methods and functions described
76             below and call them directly.
77              
78             package main;
79             use MyBaseClass;
80             use Class::Mutator 'mutate';
81             mutate( MyBaseClass->new(), ... );
82              
83             =cut
84              
85             package Class::Mutator;
86             $VERSION='0.04';
87              
88             @EXPORT_OK = qw( mutate unmutate apply_mutation modify_mutation_list
89             get_mutation_list build_mutation_package );
90              
91             sub import {
92 2     2   4393 my $class = shift;
93              
94 2 100       12 if ( ! scalar @_ ) {
    50          
95 1         16 return;
96             } elsif ( $_[0] eq '-isasubclass' ) {
97 1   33     10 my $target_class = $_[1] || ( caller )[0];
98 1     1   29447 no strict 'refs';
  1         3  
  1         188  
99 1         2184 push @{"$target_class\::ISA"}, $class
  0         0  
100 1 50       3 unless ( grep { $_ eq $class } @{"$target_class\::ISA"} );
  1         8  
101             } else {
102 0 0       0 require Exporter and goto &Exporter::import # lazy Exporter
103             }
104             }
105              
106 1     1   9 use strict;
  1         5  
  1         595  
107              
108             =head1 METHODS
109              
110             These methods provide the module's public object-oriented interface.
111              
112             =head2 mutate
113              
114             $object->mutate( @packages );
115              
116             Adds a mutation.
117              
118             =cut
119              
120             sub mutate {
121 3     3 1 1567 my $self = shift;
122 3         6 my @packages = @_;
123 3         10 apply_mutation($self, '+', @packages);
124             }
125              
126             =head2 unmutate
127              
128             $object->unmutate( @packages );
129              
130             Remove mutation abilities via a package
131              
132             =cut
133              
134             sub unmutate {
135 3     3 1 828 my $self = shift;
136 3         8 my @packages = @_;
137 3         7 apply_mutation($self, '-', @packages);
138             }
139              
140              
141             =head1 FUNCTIONS
142              
143             These functions are used internally to support the methods described
144             above.
145              
146             =head2 apply_mutation
147              
148             $reblessed_object = apply_mutation( $object, $op, @packages );
149              
150             Builds a new package list, based on the current package for a given
151             object, the operator (either "+" or "-"), and the packages to be
152             added or removed.
153              
154             =cut
155              
156             sub apply_mutation {
157 6     6 1 10 my $self = shift;
158 6         19 bless $self, build_mutation_package( modify_mutation_list( $self, @_ ) )
159             }
160              
161             =head2 get_mutation_list
162              
163             @packages = get_mutation_list($object_or_class);
164              
165             Returns the list of classes a mutated object is based on. If the
166             object is not a mutant, its normal class name will be returned.
167              
168             =cut
169              
170             sub get_mutation_list {
171 6     6 1 8 my $self = shift;
172              
173 6   33     16 my $curr = ref($self) || $self;
174              
175 6 100       29 if ($curr =~ s/^Class::Mutator:://) {
176 5         15 return map { s/__/::/g; $_ } split /::/,$curr;
  11         17  
  11         30  
177             } else {
178 1         3 return $curr
179             }
180             }
181              
182             =head2 build_mutation_package
183              
184             $new_class_name = build_mutation_package(@packages);
185              
186             Builds the new mutation package. Returns the name of the new class.
187              
188             =cut
189              
190             sub build_mutation_package {
191 6     6 1 12 my @class_list = @_;
192 6         11 my @ingredients = map { s/::/__/g; $_ } @class_list;
  12         16  
  12         41  
193 6         16 my $package_name = 'Class::Mutator::'.join('::',@ingredients);
194              
195             # If our target class has an inheritance tree, we've already
196             # set it up on a previous invocation, so there's nothing to do.
197 1     1   9 no strict 'refs';
  1         17  
  1         445  
198 6 100       8 unless ( scalar @{$package_name . '::ISA'} ) {
  6         72  
199 5         10 @{$package_name . '::ISA'} = reverse @class_list;
  5         97  
200             }
201 6         42 return $package_name;
202             }
203              
204             =head2 modify_mutation_list
205              
206             @packages = modify_mutation_list($object_or_class, $op, @packages);
207              
208             Builds a new package list based on the current packages list and the
209             operation and package (the operation is upon) handed to this method.
210              
211             =cut
212              
213             sub modify_mutation_list {
214 6     6 1 7 my $self = shift;
215 6         13 my ($op, @packages) = @_;
216              
217 6         13 my @active_classes = get_mutation_list( $self );
218              
219 6         13 foreach my $package ( @packages ) {
220 8         13 @active_classes = grep { $_ ne $package } @active_classes;
  15         48  
221             }
222              
223 6 100       17 if ($op eq '+') {
    50          
224 3         6 push(@active_classes, @packages);
225             } elsif ($op eq '-') {
226             # We've already got this functionality out of the grep above
227             } else {
228             # Invalid operation
229             }
230              
231 6         22 return @active_classes;
232             }
233              
234             =head1 AUTHORS
235              
236             Greg McCarroll
237             Mail : greg@mccarroll.demon.co.uk
238             Jabber : greg@jabber.mccarroll.org.uk
239             Homepage : http://www.mccarroll.org.uk/~gem/
240              
241             Matthew Simon Cavalletto
242             Mail : simonm@cavalletto.org,
243             Homepage : http://www.evoscript.org/
244              
245             =cut
246              
247             1;