File Coverage

lib/Badger/Class/Methods.pm
Criterion Covered Total %
statement 77 77 100.0
branch 20 26 76.9
condition 12 17 70.5
subroutine 21 21 100.0
pod 10 10 100.0
total 140 151 92.7


line stmt bran cond sub pod time code
1             #========================================================================
2             #
3             # Badger::Class::Methods
4             #
5             # DESCRIPTION
6             # Class mixin module for adding methods to a class.
7             #
8             # AUTHOR
9             # Andy Wardley
10             #
11             #========================================================================
12              
13             package Badger::Class::Methods;
14              
15 70     70   483 use Carp;
  70         111  
  70         8714  
16             use Badger::Class
17             version => 0.01,
18             debug => 0,
19             base => 'Badger::Base',
20             import => 'class BCLASS',
21             constants => 'DELIMITER ARRAY HASH PKG CODE',
22             utils => 'is_object',
23             exports => {
24             hooks => {
25             init => \&initialiser,
26 70         256 map { $_ => [\&generate, 1] }
  490         2267  
27             qw( accessors mutators get set slots hash auto_can )
28             },
29             },
30             messages => {
31             no_target => 'No target class specified to generate methods for',
32             no_type => 'No method type specified to generate',
33             no_methods => 'No %s specified to generate',
34             bad_method => 'Invalid %s method: %s',
35             bad_type => 'Invalid method generator specified: %s',
36 70     70   443 };
  70         154  
37              
38             # method aliases
39             *get = \&accessors;
40             *set = \&mutators;
41              
42             our $AUTOLOAD;
43              
44             sub generate {
45 8     8 1 12 my $class = shift;
46 8   50     14 my $target = shift
47             || return $class->error_msg('no_target');
48 8   50     13 my $type = shift
49             || return $class->error_msg('no_type');
50 8   50     16 my $methods = shift
51             || return $class->error_msg( no_methods => $type );
52 8   50     36 my $code = $class->can($type)
53             || return $class->error_msg( bad_type => $type );
54              
55 8         10 $class->debug("generate($target, $type, $methods)") if DEBUG;
56            
57 8         16 $code->($class, $target, $methods);
58             }
59              
60             sub accessors {
61 258     258 1 741 my ($class, $target, $methods) = shift->args(@_);
62              
63             $target->import_symbol(
64             $_ => $class->accessor($_)
65 258         873 ) for @$methods;
66             }
67              
68             sub accessor {
69 516     516 1 877 my ($self, $name) = @_;
70             return sub {
71 677     677   2686 $_[0]->{ $name };
72 516         2188 };
73             }
74              
75             sub mutators {
76 87     87 1 305 my ($class, $target, $methods) = shift->args(@_);
77              
78             $target->import_symbol(
79             $_ => $class->mutator($_)
80 87         289 ) for @$methods;
81             }
82              
83             sub mutator {
84 92     92 1 180 my ($self, $name) = @_;
85             return sub {
86             # You wouldn't ever want to write a real subroutine like this.
87             # But that's OK, because we're here to do it for you. You get
88             # the efficiency without having to ever look at code like this:
89             @_ == 2
90             ? ($_[0]->{ $name } = $_[1])
91 55 100   55   313 : $_[0]->{ $name };
92 92         460 };
93             }
94              
95             sub hash {
96 1     1 1 4 my ($class, $target, $methods) = shift->args(@_);
97              
98 1         3 foreach (@$methods) {
99 1         2 my $name = $_; # new lexical var for closure
100             $target->import_symbol(
101             $name => sub {
102             # return hash ref when called without args
103 8 100   8   20 return $_[0]->{ $name } if @_ == 1;
104            
105             # return hash item when called with one non-ref arg
106 7 100 100     37 return $_[0]->{ $name }->{ $_[1] } if @_ == 2 && ! ref $_[1];
107            
108             # add items to hash when called with hash ref or multiple args
109 2         3 my $self = shift;
110 2 100 66     10 my $items = @_ == 1 && ref $_[0] eq HASH ? shift : { @_ };
111 2         3 my $hash = $self->{ $name };
112 2         10 @$hash{ keys %$items } = values %$items;
113 2         4 return $hash;
114             }
115 1         6 );
116             }
117             }
118              
119             sub initialiser {
120 3     3 1 13 my ($class, $target, $methods) = shift->args(@_);
121              
122             $target->import_symbol(
123             init => sub {
124 3     3   12 my ($self, $config) = @_;
125 3         18 $self->{ config } = $config;
126 3         7 foreach my $name (@$methods) {
127 3         18 $self->$name($config);
128             }
129 3         7 return $self;
130             }
131 3         45 );
132             }
133              
134             sub slots {
135 3     3 1 12 my ($class, $target, $methods) = shift->args(@_);
136 3         5 my $index = 0;
137              
138 3         6 foreach my $method (@$methods) {
139 9         10 my $i = $index++; # new lexical var for closure
140             $target->import_symbol(
141             $method => sub {
142 9 50   9   40 return @_ > 1
143             ? ($_[0]->[$i] = $_[1])
144             : $_[0]->[$i];
145             }
146 9         32 );
147             }
148             }
149              
150             sub auto_can {
151 143     143 1 442 my ($class, $target, $methods) = shift->args(@_);
152              
153 143 50       383 die "auto_can only support a single method at this time\n"
154             if @$methods != 1;
155            
156 143         240 my $method = shift @$methods;
157              
158 143 50       308 croak "Invalid auto_can method specified: $method\n"
159             if ref $method eq CODE;
160            
161             # avoid runaways
162 143         208 my $seen = { };
163            
164 143         158 $class->debug("installing AUTOLOAD and can() in $target") if DEBUG;
165              
166             $target->import_symbol(
167             can => sub {
168 18     18   33 my ($this, $name, @args) = @_;
        18      
        10      
169 18         18 $class->debug("looking to see if $this can $name()") if DEBUG;
170              
171             # This avoids runaways where can() calls itself repeatedly, but
172             # doesn't prevent can() from being called several times for the
173             # same item.
174 18 50       34 return if $seen->{ $name };
175 18         45 local $seen->{ $name } = 1;
176              
177 18   100     126 return $this->SUPER::can($name)
178             || $this->$method($name, @args);
179             }
180 143         912 );
181              
182             $target->import_symbol(
183             AUTOLOAD => sub {
184 20     20   80 my ($this, @args) = @_;
185 20         113 my ($name) = ($AUTOLOAD =~ /([^:]+)$/ );
186 20 100       490 return if $name eq 'DESTROY';
187 10 100       27 if (my $method = $this->can($name, @args)) {
188 7         33 my $that = class($this);
189 7         9 $class->debug("$class installing $name method in $that") if DEBUG;
190 7         25 $that->method( $name => $method );
191 7         22 return $method->($this, @args);
192             }
193              
194             # Hmmm... what if $this isn't a subclass of Badger::Base?
195 3         39 return $this->error_msg( bad_method => $name, ref $this, (caller())[1,2] );
196             }
197 143         754 );
198              
199 143         280 $class->debug("installed AUTOLOAD and can() in $target") if DEBUG;
200             }
201              
202             sub args {
203 495     495 1 668 my $class = shift;
204 495         561 my $target = shift;
205 495 50       990 my $methods = @_ == 1 ? shift : [ @_ ];
206              
207             # update $target to a Badger::Class object if not already one
208 495 50       1148 $target = class($target)
209             unless is_object(BCLASS, $target);
210              
211             # split text string into list ref of method names
212 495 100       2974 $methods = [ split(DELIMITER, $methods) ]
213             unless ref $methods eq ARRAY;
214            
215 495         1433 return ($class, $target, $methods);
216             }
217            
218              
219              
220             1;
221              
222             __END__