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 20 20 100.0
pod 10 10 100.0
total 139 150 92.6


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     71   458 use Carp;
  70         131  
  70         9488  
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         318 map { $_ => [\&generate, 1] }
  490         2576  
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   489 };
  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     17 my $target = shift
47             || return $class->error_msg('no_target');
48 8   50     26 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     41 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         20 $code->($class, $target, $methods);
58             }
59              
60             sub accessors {
61 258     258 1 796 my ($class, $target, $methods) = shift->args(@_);
62              
63             $target->import_symbol(
64             $_ => $class->accessor($_)
65 258         945 ) for @$methods;
66             }
67              
68             sub accessor {
69 516     516 1 989 my ($self, $name) = @_;
70             return sub {
71 684     684   3190 $_[0]->{ $name };
72 516         2477 };
73             }
74              
75             sub mutators {
76 87     87 1 296 my ($class, $target, $methods) = shift->args(@_);
77              
78             $target->import_symbol(
79             $_ => $class->mutator($_)
80 87         322 ) for @$methods;
81             }
82              
83             sub mutator {
84 92     92 1 197 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   343 : $_[0]->{ $name };
92 92         530 };
93             }
94              
95             sub hash {
96 1     1 1 4 my ($class, $target, $methods) = shift->args(@_);
97              
98 1         3 foreach (@$methods) {
99 1         1 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   24 return $_[0]->{ $name } if @_ == 1;
104            
105             # return hash item when called with one non-ref arg
106 7 100 100     43 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         5 my $hash = $self->{ $name };
112 2         8 @$hash{ keys %$items } = values %$items;
113 2         5 return $hash;
114             }
115 1         6 );
116             }
117             }
118              
119             sub initialiser {
120 3     3 1 12 my ($class, $target, $methods) = shift->args(@_);
121              
122             $target->import_symbol(
123             init => sub {
124 3     3   10 my ($self, $config) = @_;
125 3         16 $self->{ config } = $config;
126 3         10 foreach my $name (@$methods) {
127 3         17 $self->$name($config);
128             }
129 3         7 return $self;
130             }
131 3         37 );
132             }
133              
134             sub slots {
135 3     3 1 9 my ($class, $target, $methods) = shift->args(@_);
136 3         7 my $index = 0;
137              
138 3         7 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   54 return @_ > 1
143             ? ($_[0]->[$i] = $_[1])
144             : $_[0]->[$i];
145             }
146 9         37 );
147             }
148             }
149              
150             sub auto_can {
151 143     143 1 421 my ($class, $target, $methods) = shift->args(@_);
152              
153 143 50       435 die "auto_can only support a single method at this time\n"
154             if @$methods != 1;
155            
156 143         260 my $method = shift @$methods;
157              
158 143 50       362 croak "Invalid auto_can method specified: $method\n"
159             if ref $method eq CODE;
160            
161             # avoid runaways
162 143         221 my $seen = { };
163            
164 143         181 $class->debug("installing AUTOLOAD and can() in $target") if DEBUG;
165              
166             $target->import_symbol(
167             can => sub {
168 18     18   41 my ($this, $name, @args) = @_;
        12      
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       54 return if $seen->{ $name };
175 18         40 local $seen->{ $name } = 1;
176              
177 18   100     136 return $this->SUPER::can($name)
178             || $this->$method($name, @args);
179             }
180 143         1004 );
181              
182             $target->import_symbol(
183             AUTOLOAD => sub {
184 20     20   122 my ($this, @args) = @_;
185 20         135 my ($name) = ($AUTOLOAD =~ /([^:]+)$/ );
186 20 100       615 return if $name eq 'DESTROY';
187 10 100       38 if (my $method = $this->can($name, @args)) {
188 7         40 my $that = class($this);
189 7         9 $class->debug("$class installing $name method in $that") if DEBUG;
190 7         28 $that->method( $name => $method );
191 7         16 return $method->($this, @args);
192             }
193              
194             # Hmmm... what if $this isn't a subclass of Badger::Base?
195 3         37 return $this->error_msg( bad_method => $name, ref $this, (caller())[1,2] );
196             }
197 143         800 );
198              
199 143         321 $class->debug("installed AUTOLOAD and can() in $target") if DEBUG;
200             }
201              
202             sub args {
203 495     495 1 728 my $class = shift;
204 495         622 my $target = shift;
205 495 50       1004 my $methods = @_ == 1 ? shift : [ @_ ];
206              
207             # update $target to a Badger::Class object if not already one
208 495 50       1114 $target = class($target)
209             unless is_object(BCLASS, $target);
210              
211             # split text string into list ref of method names
212 495 100       3378 $methods = [ split(DELIMITER, $methods) ]
213             unless ref $methods eq ARRAY;
214            
215 495         1603 return ($class, $target, $methods);
216             }
217            
218              
219              
220             1;
221              
222             __END__