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 22 95.4
pod 10 10 100.0
total 140 152 92.1


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   510 use Carp;
  70         133  
  70         9732  
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         317 map { $_ => [\&generate, 1] }
  490         2577  
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   528 };
  70         162  
37              
38             # method aliases
39             *get = \&accessors;
40             *set = \&mutators;
41              
42             our $AUTOLOAD;
43              
44             sub generate {
45 8     8 1 18 my $class = shift;
46 8   50     15 my $target = shift
47             || return $class->error_msg('no_target');
48 8   50     17 my $type = shift
49             || return $class->error_msg('no_type');
50 8   50     14 my $methods = shift
51             || return $class->error_msg( no_methods => $type );
52 8   50     40 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         21 $code->($class, $target, $methods);
58             }
59              
60             sub accessors {
61 258     258 1 804 my ($class, $target, $methods) = shift->args(@_);
62              
63             $target->import_symbol(
64             $_ => $class->accessor($_)
65 258         1027 ) for @$methods;
66             }
67              
68             sub accessor {
69 516     516 1 989 my ($self, $name) = @_;
70             return sub {
71 684     684   3258 $_[0]->{ $name };
72 516         2560 };
73             }
74              
75             sub mutators {
76 87     87 1 339 my ($class, $target, $methods) = shift->args(@_);
77              
78             $target->import_symbol(
79             $_ => $class->mutator($_)
80 87         329 ) for @$methods;
81             }
82              
83             sub mutator {
84 92     92 1 220 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   367 : $_[0]->{ $name };
92 92         545 };
93             }
94              
95             sub hash {
96 1     1 1 2 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   23 return $_[0]->{ $name } if @_ == 1;
104            
105             # return hash item when called with one non-ref arg
106 7 100 100     36 return $_[0]->{ $name }->{ $_[1] } if @_ == 2 && ! ref $_[1];
107            
108             # add items to hash when called with hash ref or multiple args
109 2         4 my $self = shift;
110 2 100 66     13 my $items = @_ == 1 && ref $_[0] eq HASH ? shift : { @_ };
111 2         6 my $hash = $self->{ $name };
112 2         11 @$hash{ keys %$items } = values %$items;
113 2         4 return $hash;
114             }
115 1         5 );
116             }
117             }
118              
119             sub initialiser {
120 3     3 1 17 my ($class, $target, $methods) = shift->args(@_);
121              
122             $target->import_symbol(
123             init => sub {
124 3     3   8 my ($self, $config) = @_;
125 3         14 $self->{ config } = $config;
126 3         9 foreach my $name (@$methods) {
127 3         13 $self->$name($config);
128             }
129 3         5 return $self;
130             }
131 3         21 );
132             }
133              
134             sub slots {
135 3     3 1 13 my ($class, $target, $methods) = shift->args(@_);
136 3         6 my $index = 0;
137              
138 3         9 foreach my $method (@$methods) {
139 9         13 my $i = $index++; # new lexical var for closure
140             $target->import_symbol(
141             $method => sub {
142 9 50   9   56 return @_ > 1
143             ? ($_[0]->[$i] = $_[1])
144             : $_[0]->[$i];
145             }
146 9         32 );
147             }
148             }
149              
150             sub auto_can {
151 143     143 1 448 my ($class, $target, $methods) = shift->args(@_);
152              
153 143 50       456 die "auto_can only support a single method at this time\n"
154             if @$methods != 1;
155            
156 143         259 my $method = shift @$methods;
157              
158 143 50       347 croak "Invalid auto_can method specified: $method\n"
159             if ref $method eq CODE;
160            
161             # avoid runaways
162 143         261 my $seen = { };
163            
164 143         183 $class->debug("installing AUTOLOAD and can() in $target") if DEBUG;
165              
166             $target->import_symbol(
167             can => sub {
168 18     18   42 my ($this, $name, @args) = @_;
        18      
        9      
        0      
169 18         21 $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       45 return if $seen->{ $name };
175 18         39 local $seen->{ $name } = 1;
176              
177 18   100     190 return $this->SUPER::can($name)
178             || $this->$method($name, @args);
179             }
180 143         970 );
181              
182             $target->import_symbol(
183             AUTOLOAD => sub {
184 20     20   101 my ($this, @args) = @_;
185 20         145 my ($name) = ($AUTOLOAD =~ /([^:]+)$/ );
186 20 100       620 return if $name eq 'DESTROY';
187 10 100       32 if (my $method = $this->can($name, @args)) {
188 7         36 my $that = class($this);
189 7         13 $class->debug("$class installing $name method in $that") if DEBUG;
190 7         24 $that->method( $name => $method );
191 7         17 return $method->($this, @args);
192             }
193              
194             # Hmmm... what if $this isn't a subclass of Badger::Base?
195 3         46 return $this->error_msg( bad_method => $name, ref $this, (caller())[1,2] );
196             }
197 143         867 );
198              
199 143         315 $class->debug("installed AUTOLOAD and can() in $target") if DEBUG;
200             }
201              
202             sub args {
203 495     495 1 736 my $class = shift;
204 495         642 my $target = shift;
205 495 50       1048 my $methods = @_ == 1 ? shift : [ @_ ];
206              
207             # update $target to a Badger::Class object if not already one
208 495 50       1205 $target = class($target)
209             unless is_object(BCLASS, $target);
210              
211             # split text string into list ref of method names
212 495 100       3424 $methods = [ split(DELIMITER, $methods) ]
213             unless ref $methods eq ARRAY;
214            
215 495         1770 return ($class, $target, $methods);
216             }
217            
218              
219              
220             1;
221              
222             __END__