File Coverage

blib/lib/Class/MakeMethods.pm
Criterion Covered Total %
statement 117 162 72.2
branch 60 102 58.8
condition 24 45 53.3
subroutine 17 18 94.4
pod 1 1 100.0
total 219 328 66.7


line stmt bran cond sub pod time code
1             ### Class::MakeMethods
2             # Copyright 2002, 2003 Matthew Simon Cavalletto
3             # See documentation, license, and other information after _END_.
4              
5             package Class::MakeMethods;
6              
7             require 5.00307; # for the UNIVERSAL::isa method.
8 116     116   16470 use strict;
  116         226  
  116         5366  
9 116     116   6162 use Carp;
  116         213  
  116         9836  
10              
11 116     116   798 use vars qw( $VERSION );
  116         237  
  116         9571  
12             $VERSION = 1.010;
13              
14 116     116   646 use vars qw( %CONTEXT %DIAGNOSTICS );
  116         508  
  116         22640  
15              
16             ########################################################################
17             ### MODULE IMPORT: import(), _import_version()
18             ########################################################################
19              
20             sub import {
21 592     592   3053 my $class = shift;
22              
23 592 50 66     9355 if ( scalar @_ and $_[0] =~ m/^\d/ ) {
24 0         0 $class->_import_version( shift );
25             }
26            
27 592 100 66     4056 if ( scalar @_ == 1 and $_[0] eq '-isasubclass' ) {
28 469         18274 shift;
29 469         2071 my $target_class = ( caller )[0];
30 116     116   942 no strict;
  116         230  
  116         15311  
31 469         1059 push @{"$target_class\::ISA"}, $class;
  469         8538  
32             }
33            
34 592 100       31848 $class->make( @_ ) if ( scalar @_ );
35             }
36              
37             sub _import_version {
38 0     0   0 my $class = shift;
39 0         0 my $wanted = shift;
40            
41 116     116   759 no strict;
  116         265  
  116         211978  
42 0         0 my $version = ${ $class.'::VERSION '};
  0         0  
43            
44             # If passed a version number, ensure that we measure up.
45             # Based on similar functionality in Exporter.pm
46 0 0 0     0 if ( ! $version or $version < $wanted ) {
47 0         0 my $file = "$class.pm";
48 0         0 $file =~ s!::!/!g;
49 0 0       0 $file = $INC{$file} ? " ($INC{$file})" : '';
50 0   0     0 _diagnostic('mm_version_fail', $class, $wanted, $version || '(undef)', $file);
51             }
52             }
53              
54             ########################################################################
55             ### METHOD GENERATION: make()
56             ########################################################################
57              
58             sub make {
59 187     187 1 730 local $CONTEXT{MakerClass} = shift;
60            
61             # Find the first class in the caller() stack that's not a subclass of us
62 187         438 local $CONTEXT{TargetClass};
63 187         334 my $i = 0;
64 187         336 do {
65 355         7471 $CONTEXT{TargetClass} = ( caller($i ++) )[0];
66             } while UNIVERSAL::isa($CONTEXT{TargetClass}, __PACKAGE__ );
67            
68 187         347 my @methods;
69            
70             # For compatibility with 5.004, which fails to splice use's constant @_
71 187         777 my @declarations = @_;
72            
73 187 50       885 if (@_ % 2) { _diagnostic('make_odd_args', $CONTEXT{MakerClass}); }
  0         0  
74 187         690 while ( scalar @declarations ) {
75             # The list passed to import should alternate between the names of the
76             # meta-method to call to generate the methods, and arguments to it.
77 436         1560 my ($name, $args) = splice(@declarations, 0, 2);
78 436 50       1661 unless ( defined $name ) {
79 0         0 croak "Undefined name";
80             }
81            
82             # Leading dash on the first argument of a pair means it's a
83             # global/general option to be stored in CONTEXT.
84 436 100       1921 if ( $name =~ s/^\-// ) {
85            
86             # To prevent difficult-to-predict retroactive behaviour, start by
87             # flushing any pending methods before letting settings take effect
88 46 50       162 if ( scalar @methods ) {
89 0         0 _install_methods( $CONTEXT{MakerClass}, @methods );
90 0         0 @methods = ();
91             }
92            
93 46 100       177 if ( $name eq 'MakerClass' ) {
94             # Switch base package for remainder of args
95 15         44 $CONTEXT{MakerClass} = _find_subclass($CONTEXT{MakerClass}, $args);
96             } else {
97 31         70 $CONTEXT{$name} = $args;
98             }
99            
100 46         133 next;
101             }
102            
103             # Argument normalization
104 390 100       2478 my @args = (
    100          
105             ! ref($args) ? split(' ', $args) : # If a string, it is split on spaces.
106             ref($args) eq 'ARRAY' ? (@$args) : # If an arrayref, use its contents.
107             ( $args ) # If a hashref, it is used directly
108             );
109              
110             # If the type argument contains an array of method types, do the first
111             # now, and put the others back in the queue to be processed subsequently.
112 390 100       1338 if ( ref($name) eq 'ARRAY' ) {
113 3         11 ($name, my @name) = @$name;
114 3         7 unshift @declarations, map { $_=>[@args] } @name;
  6         21  
115             }
116            
117             # If the type argument contains space characters, use the first word
118             # as the type, and prepend the remaining items to the argument list.
119 390 100       1737 if ( $name =~ /\s/ ) {
120 64         333 my @items = split ' ', $name;
121 64         140 $name = shift( @items );
122 64         453 unshift @args, @items;
123             }
124            
125             # If name contains a colon or double colon, treat the preceeding part
126             # as the subclass name but only for this one set of methods.
127 390 100       3372 local $CONTEXT{MakerClass} = _find_subclass($CONTEXT{MakerClass}, $1)
128             if ($name =~ s/^(.*?)\:{1,2}(\w+)$/$2/);
129            
130             # Meta-method invocation via named_method or direct method call
131 390 50       7927 my @results = (
    100          
132             $CONTEXT{MakerClass}->can('named_method') ?
133             $CONTEXT{MakerClass}->named_method( $name, @args ) :
134             $CONTEXT{MakerClass}->can($name) ?
135             $CONTEXT{MakerClass}->$name( @args ) :
136             croak "Can't generate $CONTEXT{MakerClass}->$name() methods"
137             );
138             # warn "$CONTEXT{MakerClass} $name - ", join(', ', @results) . "\n";
139            
140             ### A method-generator may be implemented in any of the following ways:
141            
142             # SELF-CONTAINED: It may return nothing, if there are no methods
143             # to install, or if it has installed the methods itself.
144             # (We also accept a single false value, for backward compatibility
145             # with generators that are written as foreach loops, which return ''!)
146 390 100 66     6993 if ( ! scalar @results or scalar @results == 1 and ! $results[0] ) { }
    100 66        
    100 100        
    100 66        
    50          
147            
148             # ALIAS: It may return a string containing a meta-method type to run
149             # instead. Put the arguments back in the queue and go through again.
150 514         4618 elsif ( scalar @results == 1 and ! ref $results[0]) {
151 34         179 unshift @declarations, $results[0], \@args;
152             }
153            
154             # REWRITER: It may return one or more array reference containing a meta-
155             # method type and arguments which should be created to complete this
156             # request. Put the arguments back in the queue and go through again.
157             elsif ( ! grep { ref $_ ne 'ARRAY' } @results ) {
158 46         102 unshift @declarations, ( map { shift(@$_), $_ } @results );
  47         303  
159             }
160            
161             # CODE REFS: It may provide a list of name, code pairs to install
162             elsif ( ! scalar @results % 2 and ! ref $results[0] ) {
163 96         440 push @methods, @results;
164             }
165            
166             # GENERATOR OBJECT: It may return an object reference which will construct
167             # the relevant methods.
168             elsif ( UNIVERSAL::can( $results[0], 'make_methods' ) ) {
169 211         1204 push @methods, ( shift @results )->make_methods(@results, @args);
170             }
171            
172             else {
173 0         0 _diagnostic('make_bad_meta', $name, join(', ', map "'$_'", @results));
174             }
175             }
176            
177 187         1030 _install_methods( $CONTEXT{MakerClass}, @methods );
178            
179 187         248842 return;
180             }
181              
182             ########################################################################
183             ### DECLARATION PARSING: _get_declarations()
184             ########################################################################
185              
186             sub _get_declarations {
187 89     89   129 my $class = shift;
188            
189 89         110 my @results;
190             my %defaults;
191            
192 89         281 while (scalar @_) {
193 118         165 my $m_name = shift @_;
194 118 50 33     858 if ( ! defined $m_name or ! length $m_name ) {
    50          
    100          
    50          
    0          
195 0         0 _diagnostic('make_empty')
196             }
197              
198             # Various forms of default parameters
199             elsif ( substr($m_name, 0, 1) eq '-' ) {
200 0 0       0 if ( substr($m_name, 1, 1) ne '-' ) {
    0          
201             # Parse default values in the format "-param => value"
202 0         0 $defaults{ substr($m_name, 1) } = shift @_;
203             } elsif ( length($m_name) == 2 ) {
204             # Parse hash of default values in the format "-- => { ... }"
205 0 0       0 ref($_[0]) eq 'HASH' or _diagnostic('make_unsupported', $m_name.$_[0]);
206 0         0 %defaults = ( %defaults, %{ shift @_ } );
  0         0  
207             } else {
208             # Parse "special" arguments in the format "--foobar"
209 0         0 $defaults{ '--' } .= $m_name;
210             }
211             }
212            
213             # Parse string and string-then-hash declarations
214             elsif ( ! ref $m_name ) {
215 96 100 100     822 if ( scalar @_ and ref $_[0] eq 'HASH' and ! exists $_[0]->{'name'} ) {
      66        
216 24         39 push @results, { %defaults, 'name' => $m_name, %{ shift @_ } };
  24         150  
217             } else {
218 72         387 push @results, { %defaults, 'name' => $m_name };
219             }
220             }
221            
222             # Parse hash-only declarations
223             elsif ( ref $m_name eq 'HASH' ) {
224 22 50       62 if ( length $m_name->{'name'} ) {
225 22         151 push @results, { %defaults, %$m_name };
226             } else {
227 0         0 _diagnostic('make_noname');
228             }
229             }
230            
231             # Normalize: If we've got an array of names, replace it with those names
232             elsif ( ref $m_name eq 'ARRAY' ) {
233 0         0 my @items = @{ $m_name };
  0         0  
234             # If array is followed by an params hash, each one gets the same params
235 0 0 0     0 if ( scalar @_ and ref $_[0] eq 'HASH' and ! exists $_[0]->{'name'} ) {
      0        
236 0         0 my $params = shift;
237 0         0 @items = map { $_, $params } @items
  0         0  
238             }
239 0         0 unshift @_, @items;
240 0         0 next;
241             }
242            
243             else {
244 0         0 _diagnostic('make_unsupported', $m_name);
245             }
246            
247             }
248            
249 89         330 return @results;
250             }
251              
252             ########################################################################
253             ### FUNCTION INSTALLATION: _install_methods()
254             ########################################################################
255              
256             sub _install_methods {
257 187     187   9514 my ($class, %methods) = @_;
258            
259 116     116   1392 no strict 'refs';
  116         489  
  116         63618  
260            
261             # print STDERR "CLASS: $class\n";
262 187         536 my $package = $CONTEXT{TargetClass};
263            
264 187         289 my ($name, $code);
265 187         971 while (($name, $code) = each %methods) {
266            
267             # Skip this if the target package already has a function by the given name.
268 1392         9210 next if ( ! $CONTEXT{ForceInstall} and
269 1403 100 100     4157 defined *{$package. '::'. $name}{CODE} );
270            
271 1398 50       16077 if ( ! ref $code ) {
    50          
272 0         0 local $SIG{__DIE__};
273 0         0 local $^W;
274 0         0 my $coderef = eval $code;
275 0 0       0 if ( $@ ) {
    0          
276 0         0 _diagnostic('inst_eval_syntax', $name, $@, $code);
277             } elsif ( ref $coderef ne 'CODE' ) {
278 0         0 _diagnostic('inst_eval_result', $name, $coderef, $code);
279             }
280 0         0 $code = $coderef;
281             } elsif ( ref $code ne 'CODE' ) {
282 0         0 _diagnostic('inst_result', $name, $code);
283             }
284            
285             # Add the code refence to the target package
286             # _diagnostic('debug_install', $package, $name, $code);
287 1398 100       4282 local $^W = 0 if ( $CONTEXT{ForceInstall} );
288 1398         1843 *{$package . '::' . $name} = $code;
  1398         6194  
289              
290             }
291 187         789 return;
292             }
293              
294             ########################################################################
295             ### SUBCLASS LOADING: _find_subclass()
296             ########################################################################
297              
298             # $pckg = _find_subclass( $class, $optional_package_name );
299             sub _find_subclass {
300 5182     5182   14794 my $class = shift;
301 5182 50       17394 my $package = shift or die "No package for _find_subclass";
302            
303 5182 100       23586 $package = $package =~ s/^::// ? $package :
304             "Class::MakeMethods::$package";
305            
306 5182         23334 (my $file = $package . '.pm' ) =~ s|::|/|go;
307 5182 100       33235 return $package if ( $::INC{ $file } );
308            
309 116     116   981 no strict 'refs';
  116         333  
  116         24447  
310 148 100       274 return $package if ( @{$package . '::ISA'} );
  148         2501  
311            
312 147         1475 local $SIG{__DIE__} = '';
313 147         327 eval { require $file };
  147         149432  
314 147         862 $::INC{ $package } = $::INC{ $file };
315 147 50       4731 if ( $@ ) { _diagnostic('mm_package_fail', $package, $@) }
  0         0  
316            
317 147         1926 return $package
318             }
319              
320             ########################################################################
321             ### CONTEXT: _context(), %CONTEXT
322             ########################################################################
323              
324             sub _context {
325 221     221   455 my $class = shift;
326 221 50       875 return %CONTEXT if ( ! scalar @_ );
327 221         403 my $key = shift;
328 221 50       1709 return $CONTEXT{$key} if ( ! scalar @_ );
329 0         0 $CONTEXT{$key} = shift;
330             }
331              
332             BEGIN {
333 116   50 116   52836 $CONTEXT{Debug} ||= 0;
334             }
335              
336             ########################################################################
337             ### DIAGNOSTICS: _diagnostic(), %DIAGNOSTICS
338             ########################################################################
339              
340             sub _diagnostic {
341 2083     2083   4945 my $case = shift;
342 2083         4067 my $message = $DIAGNOSTICS{$case};
343 2083         10335 $message =~ s/\A\s*\((\w)\)\s*//;
344 2083   50     7787 my $severity = $1 || 'I';
345 2083 50       9512 if ( $severity eq 'Q' ) {
    0          
    0          
346 2083 50       9417 carp( sprintf( $message, @_ ) ) if ( $CONTEXT{Debug} );
347             } elsif ( $severity eq 'W' ) {
348 0 0         carp( sprintf( $message, @_ ) ) if ( $^W );
349             } elsif ( $severity eq 'F' ) {
350 0           croak( sprintf( $message, @_ ) )
351             } else {
352 0           confess( sprintf( $message, @_ ) )
353             }
354             }
355              
356              
357 116     116   54372 BEGIN { %DIAGNOSTICS = (
358              
359             ### BASE CLASS DIAGNOSTICS
360            
361             # _diagnostic('debug_install', $package, $name, $code)
362             debug_install => q|(W) Installing function %s::%s (%s)|,
363            
364             # _diagnostic('make_odd_args', $CONTEXT{MakerClass})
365             make_odd_args => q|(F) Odd number of arguments passed to %s method generator|,
366            
367             # _diagnostic('make_bad_meta', $name, join(', ', map "'$_'", @results)
368             make_bad_meta => q|(I) Unexpected return value from method constructor %s: %s|,
369            
370             # _diagnostic('inst_eval_syntax', $name, $@, $code)
371             inst_eval_syntax => q|(I) Unable to compile generated method %s(): %s| .
372             qq|\n (There's probably a syntax error in this generated code.)\n%s\n|,
373            
374             # _diagnostic('inst_eval_result', $name, $coderef, $code)
375             inst_eval_result => q|(I) Unexpected return value from compilation of %s(): '%s'| .
376             qq|\n (This generated code should have returned a code ref.)\n%s\n|,
377            
378             # _diagnostic('inst_result', $name, $code)
379             inst_result => q|(I) Unable to install code for %s() method: '%s'|,
380            
381             # _diagnostic('mm_package_fail', $package, $@)
382             mm_package_fail => q|(F) Unable to dynamically load %s: %s|,
383            
384             # _diagnostic('mm_version_fail', $class, $wanted, $version || '(undef)
385             mm_version_fail => q|(F) %s %s required--this is only version %s%s|,
386            
387             ### STANDARD SUBCLASS DIAGNOSTICS
388            
389             # _diagnostic('make_empty')
390             make_empty => q|(F) Can't parse meta-method declaration: argument is empty or undefined|,
391            
392             # _diagnostic('make_noname')
393             make_noname => q|(F) Can't parse meta-method declaration: missing name attribute.| .
394             qq|\n (Perhaps a trailing attributes hash has become separated from its name?)|,
395            
396             # _diagnostic('make_unsupported', $m_name)
397             make_unsupported => q|(F) Can't parse meta-method declaration: unsupported declaration type '%s'|,
398            
399             ### TEMPLATE SUBCLASS DIAGNOSTICS
400             # ToDo: Should be moved to the Class::MakeMethods::Template package
401            
402             debug_declaration => q|(Q) Meta-method declaration parsed: %s|,
403             debug_make_behave => q|(Q) Building meta-method behavior %s: %s(%s)|,
404             mmdef_not_interpretable => qq|(I) Not an interpretable meta-method: '%s'| .
405             qq|\n (Perhaps a meta-method attempted to import from a non-templated meta-method?)|,
406             make_bad_modifier => q|(F) Can't parse meta-method declaration: unknown option for %s: %s|,
407             make_bad_behavior => q|(F) Can't make method %s(): template specifies unknown behavior '%s'|,
408             behavior_mod_unknown => q|(F) Unknown modification to %s behavior: -%s|,
409             debug_template_builder => qq|(Q) Template interpretation for %s:\n%s|.
410             qq|\n---------\n%s\n---------\n|,
411             debug_template => q|(Q) Parsed template '%s': %s|,
412             debug_eval_builder => q|(Q) Compiling behavior builder '%s':| . qq|\n%s|,
413             make_behavior_mod => q|(F) Can't apply modifiers (%s) to code behavior %s|,
414             behavior_eval => q|(I) Class::MakeMethods behavior compilation error: %s| .
415             qq|\n (There's probably a syntax error in the below code.)\n%s|,
416             tmpl_unkown => q|(F) Can't interpret meta-method template: unknown template name '%s'|,
417             tmpl_empty => q|(F) Can't interpret meta-method template: argument is empty or undefined|,
418             tmpl_unsupported => q|(F) Can't interpret meta-method template: unsupported template type '%s'|,
419             ) }
420              
421             1;
422              
423             __END__