File Coverage

blib/lib/Rose/Object/MakeMethods.pm
Criterion Covered Total %
statement 107 112 95.5
branch 33 48 68.7
condition 15 33 45.4
subroutine 12 12 100.0
pod 2 4 50.0
total 169 209 80.8


line stmt bran cond sub pod time code
1             package Rose::Object::MakeMethods;
2              
3 4     4   24 use strict;
  4         6  
  4         121  
4              
5 4     4   19 use Carp();
  4         7  
  4         1284  
6              
7             our $VERSION = '0.856';
8              
9             __PACKAGE__->allow_apparent_reload(1);
10              
11             our %Made_Method_Custom;
12              
13             sub import
14             {
15 33     33   11261 my($class) = shift;
16              
17 33 100       23972 return 1 unless(@_);
18              
19 17         221 my($options, $args) = $class->_normalize_args(@_);
20              
21 17   33     149 $options->{'target_class'} ||= (caller)[0];
22              
23 17         204 $class->make_methods($options, $args);
24              
25 17         34549 return 1;
26             }
27              
28             sub make_methods
29             {
30 17     17 1 40 my($class) = shift;
31              
32 17         56 my($options, $args) = $class->_normalize_args(@_);
33              
34 17   33     55 $options->{'target_class'} ||= (caller)[0];
35              
36             #use Data::Dumper;
37             #print STDERR Dumper($options);
38             #print STDERR Dumper($args);
39              
40 17         48 while(@$args)
41             {
42 49         267 $class->__make_methods($options, shift(@$args), shift(@$args));
43             }
44              
45 17         33 return 1;
46             }
47              
48             # Can't use the class method maker easily here due to a chicken/egg
49             # situation, so this code is manually inlined.
50             my %Inheritable_Scalar;
51              
52             sub allow_apparent_reload
53             {
54 5 50   5 1 25 my($class) = ref($_[0]) ? ref(shift) : shift;
55              
56 5 100       24 if(@_)
57             {
58 4         18 return $Inheritable_Scalar{$class}{'allow_apparent_reload'} = shift;
59             }
60              
61 1 50       5 return $Inheritable_Scalar{$class}{'allow_apparent_reload'}
62             if(exists $Inheritable_Scalar{$class}{'allow_apparent_reload'});
63              
64 1         3 my @parents = ($class);
65              
66 1         3 while(my $parent = shift(@parents))
67             {
68 4     4   27 no strict 'refs';
  4         7  
  4         1304  
69 1         2 foreach my $subclass (@{$parent . '::ISA'})
  1         5  
70             {
71 1         2 push(@parents, $subclass);
72              
73 1 50       4 if(exists $Inheritable_Scalar{$subclass}{'allow_apparent_reload'})
74             {
75 1         12 return $Inheritable_Scalar{$subclass}{'allow_apparent_reload'}
76             }
77             }
78             }
79              
80 0         0 return undef;
81             }
82              
83             # XXX: This nasty hack should be unneeded now and will probably
84             # XXX: be removed some time in the future.
85             our $Preserve_Existing = 0;
86              
87             sub __make_methods
88             {
89 49     49   82 my($class) = shift;
90              
91             #my $options;
92              
93             #if(ref $_[0] eq 'HASH')
94             #{
95             # $options = shift;
96             #}
97             #else { $options = {} }
98              
99             #$options->{'target_class'} ||= (caller)[0];
100              
101 49         64 my $options = shift;
102 49         89 my $method_type = shift;
103 49         66 my $methods = shift;
104              
105 49         79 my $target_class = $options->{'target_class'};
106              
107 49         112 while(@$methods)
108             {
109 125         358 my $method_name = shift(@$methods);
110 125         180 my $method_args = shift(@$methods);
111              
112 125   50     830 my $make = $class->$method_type($method_name => $method_args, $options ||= {});
113              
114 125 50       325 Carp::croak "${class}::method_type(...) didn't return a hash ref!"
115             unless(ref $make eq 'HASH');
116              
117 4     4   21 no strict 'refs';
  4         7  
  4         697  
118              
119 125         476 METHOD: while(my($name, $code) = each(%$make))
120             {
121 301 50 33     740 Carp::croak "${class}::method_type(...) - key for $name is not a code ref!"
      66        
122             unless(ref $code eq 'CODE' || (ref $code eq 'HASH' && $code->{'make_method'}));
123              
124 301 100       2413 if(my $code = $target_class->can($name))
125             {
126 1 50 33     6 if($options->{'preserve_existing'} || $Preserve_Existing)
127             {
128 0         0 next METHOD;
129             }
130              
131 1 50       4 unless($options->{'override_existing'})
132             {
133 1 50 33     7 if($class->allow_apparent_reload && $class->apparently_made_method($code))
134             {
135 1         12 next METHOD;
136             }
137              
138 0         0 Carp::croak "Cannot create method ${target_class}::$name - method already exists";
139             }
140             }
141              
142 4     4   53 no warnings;
  4         9  
  4         3600  
143              
144 300 100       893 if(ref $code eq 'CODE')
145             {
146 296         309 *{"${target_class}::$name"} = $code;
  296         1894  
147             }
148             else
149             {
150             # XXX: Must track these separately because they do not show up as
151             # XXX: being named __ANON__ when fetching the sub_identity()
152 4         19 $Made_Method_Custom{$target_class}{$name}++;
153 4         17 $code->{'make_method'}($name, $target_class, $options);
154             }
155             }
156             }
157              
158 49         1078 return 1;
159             }
160              
161             sub apparently_made_method
162             {
163 1     1 0 2 my($class, $code) = @_;
164              
165 1         15 my($mm_class, $name) = $class->sub_identity($code);
166 1 50 33     7 return 0 unless($class && $name);
167             # XXX: RT 54444 - The formerly constant "__ANON__" sub name looks
168             # XXX: like this in newer versions of perl when running under the
169             # XXX: debugger: "__ANON__[/usr/lib/perl5/.../Some/Module.pm:123]"
170 1 50 33     11 return (($mm_class eq $class && $name =~ /^__ANON__/) ||
171             $Made_Method_Custom{$mm_class}{$name}) ? 1 : 0;
172             }
173              
174             # Code from Sub::Identify
175             sub sub_identity
176             {
177 1     1 0 2 my($class, $code) = @_;
178              
179 1         1 my @id;
180              
181 1         2 TRY:
182             {
183 1         2 local $@;
184              
185             eval # if this fails, the identity is undefined
186 1         2 {
187 1         9 require B;
188 1         9 my $cv = B::svref_2object($code);
189 1 50       23 return unless($cv->isa('B::CV'));
190 1         44 @id = ($cv->GV->STASH->NAME, $cv->GV->NAME);
191             };
192              
193             # Ignore errors
194             }
195              
196 1         4 return @id;
197             }
198              
199             # Given the example method types "bitfield" and "scalar", _normalize_args()
200             # takes args in any of these forms:
201             #
202             # { ... }, # Class options (optionally) go here
203             #
204             # scalar => 'foo',
205             #
206             # 'bitfield --opt' => [ 'a', 'b' ],
207             #
208             # 'scalar --opt2=blah' => [ 'foo' => { opt => 4, opt2 => 'blee' } ],
209             #
210             # scalar => [ 'a' => { default => 5 }, 'b' ],
211             #
212             # bitfield =>
213             # [
214             # bar => { size => 8 },
215             # baz => { size => 5, default => '00011' },
216             # ],
217             #
218             # and returns an options hashref (possibly empty) and a reference
219             # to an array that is normalized to look like this:
220             #
221             # [
222             # [
223             # 'scalar' => [ 'foo' => {} ],
224             #
225             # 'bitfield' =>
226             # [
227             # 'a' => { opt => 1 },
228             # 'b' => { opt => 1 }
229             # ],
230             #
231             # 'scalar' => [ 'foo' => { 'opt' => 4, 'opt2' => 'blee' } ],
232             #
233             # 'scalar'=>
234             # [
235             # 'a' => { 'default' => 5 },
236             # 'b' => {}
237             # ],
238             #
239             # 'bitfield' =>
240             # [
241             # 'bar' => { 'size' => 8 },
242             # 'baz' => { 'default' => '00011', 'size' => 5 }
243             # ]
244             # ]
245             # ]
246              
247             sub _normalize_args
248             {
249 34     34   70 my($class) = shift;
250              
251 34         45 my $i = 0;
252              
253 34         67 my(@normalized_args, $options);
254              
255 34         228 while(@_)
256             {
257 132   50     289 my $method_type = shift || last;
258              
259 132 100       275 if(ref $method_type)
260             {
261 34 100       108 if(ref $method_type eq 'HASH')
    50          
262             {
263 17 50       50 Carp::croak "Options hash ref provided more than once"
264             if($options);
265              
266 17         26 $options = $method_type;
267 17         53 next;
268             }
269             elsif(ref $method_type eq 'ARRAY')
270             {
271 17         58 unshift(@_, @$method_type);
272 17         44 next;
273             }
274             }
275              
276 98         109 my %method_options;
277              
278 98         112 my $i = 0;
279              
280 98         308 while($method_type =~ s/\s+--(\w+)(?:=(\w+))?//)
281             {
282 8 100 66     55 if($i++ || defined $2)
283             {
284 4         22 $method_options{$1} = $2;
285             }
286             else
287             {
288 4         23 $method_options{'interface'} = $1;
289             }
290             }
291              
292 98         212 push(@normalized_args, $method_type);
293              
294 98         126 my $args = shift;
295              
296 98 100       580 if(!ref $args)
    50          
297             {
298 21         59 $args = [ $args ];
299             }
300             elsif(ref $args ne 'ARRAY')
301             {
302 0         0 Carp::croak "Bad invocation of Rose::Object::MakeMethods";
303             }
304              
305 98         144 my @method_args;
306              
307 98         202 while(@$args)
308             {
309 250         330 my $method_name = shift(@$args);
310              
311 250 100       509 if(ref $args->[0])
312             {
313 221 50       515 unless(ref $args->[0] eq 'HASH')
314             {
315 0         0 Carp::croak "Expected hash ref or scalar after method name, but found $args->[0]";
316             }
317              
318 221         266 push(@method_args, $method_name => { %method_options, %{shift(@$args)} });
  221         1187  
319             }
320             else
321             {
322 29         143 push(@method_args, $method_name => { %method_options });
323             }
324             }
325              
326 98         355 push(@normalized_args, \@method_args);
327             }
328              
329 34   100     231 return($options || {}, \@normalized_args);
330             }
331              
332             1;
333              
334             __END__