File Coverage

blib/lib/Devel/Init.pm
Criterion Covered Total %
statement 136 187 72.7
branch 41 74 55.4
condition 8 14 57.1
subroutine 21 26 80.7
pod 4 4 100.0
total 210 305 68.8


line stmt bran cond sub pod time code
1             package Devel::Init;
2 9     9   22751 use strict;
  9         22  
  9         455  
3              
4 9         1592 use vars qw<
5             $VERSION @EXPORT_OK @Predicates
6             %Attribs @Inits $StackTrace $DefaultInit
7 9     9   46 >;
  9         17  
8             BEGIN {
9 9     9   23 $VERSION= 0.001_002;
10 9         17 $DefaultInit= 1; # RunInits('default') at INIT-time?
11              
12             # Just in case somebody wants Exporter.pm-like information:
13 9         402 @EXPORT_OK= (
14             ':Init', # Allows: sub foo :Init(...)
15             ':NoDefault', # Disables default initialization step
16             'RegisterInit', # RegisterInit(\&sub,@predicates)
17             'RunInits', # RunInits(@options,@predicates)
18             'InitBlock', # $single ||= InitBlock { ... return $init; };
19             'InitSub', # $single ||= InitSub( sub { ... return $init; } );
20             '-StackTrace', # Add a stack trace to a fatal error
21             '-NoStackTrace', # Restore default of no stack traces
22             '@Predicates', # Current list of predicates in RunInits()
23             );
24             }
25              
26             use constant {
27             # Name of sub called when "sub foo :Init" code is compiled:
28 9         7106 AttrSetter => 'MODIFY_CODE_ATTRIBUTES',
29             # Name of sub called by attributes::get() to retrieve sub attributes:
30             AttrGetter => 'FETCH_CODE_ATTRIBUTES',
31 9     9   56 };
  9         15  
32              
33             return 1; # Only subroutine definitions to be found below this point
34              
35              
36             INIT {
37 9 100   9   84 RunInits( 'default' )
38             if $DefaultInit;
39             }
40              
41              
42             sub import
43             {
44 11     11   643 my( $us, @what )= @_;
45 11         25 my $pkg= caller();
46              
47 11         19 local( $_ );
48 11         31 for( @what ) {
49 15 100       136 if( /^:Init$/ ) {
    100          
    50          
    100          
    100          
50 5         20 _exportInit( $pkg );
51             } elsif( /^:NoDefault$/ ) {
52 1         1832 $DefaultInit= 0;
53             } elsif( /^-(No)?StackTrace$/ ) {
54 0         0 $StackTrace= ! $1;
55             } elsif( /^\@(Predicates)$/ ) {
56 2         7 _export( $pkg, 'ARRAY', '@', $1 );
57             } elsif( /^&?(RunInits|RegisterInit|InitBlock|InitSub)$/ ) {
58 6         17 my $sub= $1;
59 6 100       21 $DefaultInit= 0
60             if 'RunInits' eq $sub;
61 6         17 _export( $pkg, 'CODE', '&', $sub );
62             } else {
63 1         3 _blameCaller( 1, __PACKAGE__, " does not export ($_)" );
64             }
65             }
66             }
67              
68              
69             sub _blameCaller
70             {
71 2     2   4 my( $depth, @text )= @_;
72 2         10 my( $pkg, $file, $line )= caller($depth);
73 2         31 die @text, " at $file line $line\n";
74             }
75              
76              
77             sub _export
78             {
79 8     8   42 my( $pkg, $type, $sig, $name )= @_;
80 8         24 my $glob= _getGlob( $pkg, $name );
81 8         17 my $src= *{ _getGlob( __PACKAGE__, $name ) }{$type};
  8         18  
82 8 50       54 die $sig, __PACKAGE__, "::$name is not of type $type?!"
83             if $type ne ref($src);
84 8 50       39 die __PACKAGE__, "::$name not defined?!"
85             if ! $src;
86 8 100       12 if( *{$glob}{$type} ) {
  8         29  
87 1         5 _blameCaller( 2, __PACKAGE__,
88             " won't export $sig$name over existing definition" );
89             }
90 7         8 *{$glob}= $src;
  7         2483  
91             }
92              
93              
94             sub _getGlob
95             {
96 26     26   43 my( $pkg, $name )= @_;
97 9     9   71 my $stash= do { no strict 'refs'; \%{$pkg.'::'} };
  9         18  
  9         664  
  26         27  
  26         33  
  26         73  
98 26         56 my $glob= \$stash->{$name};
99 26 100       90 if( 'GLOB' ne ref $glob ) {
100             # Must be 5.10 with its space optimization!
101             # So we need to force a real glob to be created.
102             # This means that that sub was actually defined as a constant, but
103             # we'll let the likely *{$glob}= sub ...; code trigger the warning.
104             # We only do this to prevent the completely unenlightening
105             # but fatal "not a GLOB reference" error.
106             {
107 9     9   49 no strict 'refs';
  9         14  
  9         20315  
  17         22  
108 17         21 ${$pkg.'::'.$name}= undef;
  17         76  
109             }
110 17         42 $glob= \$stash->{$name};
111 17 50       63 die "Can't coerce optimized STAB entry ($pkg\::$name) into real GLOB"
112             if 'GLOB' ne ref $glob;
113             }
114 26         62 return $glob;
115             }
116              
117              
118             sub _exportInit
119             {
120 5     5   12 my( $pkg )= @_;
121             # Export a &MODIFY_CODE_ATTRIBUTES to the calling package
122              
123 5         17 my $glob= _getGlob( $pkg, AttrSetter() );
124 5         9 my $prev= *{$glob}{CODE};
  5         18  
125 5 50       16 if( ! $prev ) {
126 5         11 *{$glob}= \&_newAttrib;
  5         15  
127             } else {
128 0         0 my $prevSetter= $prev;
129 0         0 *{$glob}= sub {
130 0     0   0 return _newAttrib( @_[0,1], $prevSetter->(@_) );
131 0         0 };
132             }
133              
134 5         17 $glob= _getGlob( $pkg, AttrGetter() );
135 5         10 $prev= *{$glob}{CODE};
  5         12  
136 5 50       16 if( ! $prev ) {
137 5         9 *{$glob}= \&_getAttribs;
  5         133  
138             } else {
139 0         0 my $prevGetter= $prev;
140 0         0 *{$glob}= sub {
141 0     0   0 return( $prevGetter->(@_), _getAttribs(@_) );
142 0         0 };
143             }
144             }
145              
146              
147             sub _getAttribs
148             {
149 0     0   0 my( $code )= @_;
150 0         0 my $addr;
151 0 0       0 if( 'CODE' eq ref $code ) {
152 0         0 $addr= 0 + $code;
153             } else {
154 0         0 die AttrGetter(), " given a non-code reference: $code";
155             }
156 0 0       0 return @{ $Attribs{ 0 + $code } || [] };
  0         0  
157             }
158              
159              
160             sub _newAttrib
161             {
162 5     5   15392 my( $pkg, $code, @attribs )= @_;
163 5         10 my @left;
164              
165 5         10 local( $_ );
166 5         17 for( @attribs ) {
167 5 50       39 if( /^Init\b/ ) {
168 5         11 push @{ $Attribs{ 0 + $code } }, $_;
  5         28  
169 5         24 s/^Init\(?//;
170 5         22 s/\)$//;
171 5         27 _registerInitFor( $pkg, $code, split /[,\s]+/, $_ );
172             } else {
173 0         0 push @left, $_;
174             }
175             }
176              
177 5         22 return @left;
178             }
179              
180              
181             sub _registerInitFor
182             {
183 15     15   44 my( $pkg, $code, @conds )= @_;
184             @conds= map {
185 15 50       34 if( /^(\\?)\@(\w+)$/ ) {
  13 50       105  
186 0         0 my( $asRef, $name )= ( $1, $2 );
187 0         0 my $av= *{ _getGlob( $pkg, $name ) }{ARRAY};
  0         0  
188 0 0       0 if( ! $av ) {
    0          
189 0         0 _blameCaller( 2,
190             "Undeclared array name, \@$pkg\::$name, in :Init(@conds)",
191             );
192             } elsif( $asRef ) {
193 0         0 $av;
194             } else {
195 0         0 @$av;
196             }
197             } elsif( ! /^\w[-.\w]*$/ ) {
198 0         0 _blameCaller( 2,
199             "Invalid predicate name, $_, in :Init(@conds)",
200             );
201             } else {
202 13         58 $_;
203             }
204             } @conds;
205 15 100       2864 push @Inits, @conds ? [ $code, @conds ] : $code;
206             }
207              
208              
209             sub InitBlock(&)
210             {
211 4     4 1 477 my( $code )= @_;
212 4         20 return InitSub( $code, 1 );
213             }
214              
215              
216             {
217             my %running;
218             my @stack;
219              
220              
221             sub InitSub
222             {
223 5     5 1 16 my( $code, $depth )= @_;
224 5   100     55 my( $pkg, $file, $line, $sub )= caller( $depth || 0 );
225 5         25 my $key= join ",", $pkg, $sub, $line, $file;
226 5         20 for my $val ( $running{$key} ) {
227 5 50       21 if( defined $val ) {
228 0         0 die _cycleReport( $val, $pkg, $sub, $line );
229             }
230 5         15 $val= @stack;
231             }
232 5         12 push @stack, $key;
233 5         9 my @ret;
234 5 50       39 if( wantarray ) {
235 0         0 @ret= $code->();
236             } else {
237 5         28 $ret[0]= $code->();
238             }
239 5         573 delete $running{$key};
240 5         8 pop @stack;
241 5 50       30 return wantarray ? @ret : $ret[0];
242             }
243              
244              
245             sub _cycleReport
246             {
247 0     0   0 my( $start, $pkg, $sub, $line )= @_;
248 0         0 my $steps= @stack - $start;
249 0         0 my $err= "Circular initialization dependency detected.\n";
250 0 0       0 if( $StackTrace ) {
251 0         0 require Carp;
252 0         0 $err= Carp::longmess( $err );
253             }
254 0         0 $err .= join ' ',
255             "The following initialization steps",
256             "form the $steps-step cycle:\n";
257 0         0 for my $i ( $start .. $#stack ) {
258 0         0 my( $pkg, $sub, $line, $file )= split /,/, $stack[$i];
259 0         0 $err .= " $pkg\::$sub() (line $line) calls\n";
260             }
261 0         0 $err .= " $pkg\::$sub().\n";
262 0         0 $err .= "$pkg\::$sub() indirectly calls itself!\n";
263 0         0 return $err;
264             }
265              
266             }
267              
268              
269             sub RegisterInit
270             {
271             # my( $code, @conds )= @_;
272 10     10 1 4219 my $pkg= caller();
273 10         44 _registerInitFor( $pkg, @_ );
274             }
275              
276              
277             sub _runOneInit
278             {
279 13     13   23 my( $code, $svFail )= @_;
280 13         15 my $error;
281 13         25 my $okay= do {
282 13         33 my $prev= $SIG{__DIE__};
283             local $SIG{__DIE__}= sub {
284 0 0   0   0 if( ! $StackTrace ) {
285 0         0 $error= $_[0];
286             } else {
287 0         0 require Carp;
288 0         0 $error= Carp::longmess( $_[0] );
289             }
290 0 0       0 die @_
291             if ! $prev;
292 0         0 $prev->( @_ );
293 13         101 };
294              
295 13         22 eval { $code->(); 1 };
  13         642  
  13         2598  
296             };
297 13 50       52 if( ! $okay ) {
298 0 0 0     0 $$svFail=
      0        
299             $StackTrace && $error
300             ? $error
301             : $@ || $error || "Unknown failure initializing";
302             }
303             }
304              
305              
306             sub _rankConds
307             {
308 29     29   55 my( @conds )= @_;
309             return map {
310 29 100       50 if( s/^not_// ) {
  24 100       145  
311 7         32 ( $_, -1 );
312             } elsif( s/^only_// ) {
313 8         40 ( $_, 2 );
314             } else {
315 9         60 ( $_, 1 );
316             }
317             } @conds;
318             }
319              
320              
321             sub _skipInit
322             {
323 17     17   30 my( $code, $hvHave, $svFail )= @_;
324 17         26 my @need;
325 17 100       96 ( $code, @need )= @$code
326             if 'ARRAY' eq ref($code);
327              
328 17         45 my %need= _rankConds( @need );
329 17         65 for my $cond ( keys %need, keys %$hvHave ) {
330 25 100 100     193 return 1 # We skipped this init; keep it around to run later
      100        
331             if 1 < abs( ($need{$cond}||0) - ($hvHave->{$cond}||0) );
332             }
333              
334 13         49 _runOneInit( $code, $svFail );
335 13         120 return 0; # We didn't skip this init; don't run it again
336             }
337              
338              
339             sub RunInits
340             {
341 12     12 1 655 local( $StackTrace )= $StackTrace;
342 12   66     106 while( @_ && $_[0] =~ /^-/ ) {
343 0         0 local $_= shift @_;
344 0 0       0 if( /^-(No)StackTrace$/i ) {
345 0         0 $StackTrace= ! $1;
346             } else {
347 0         0 _blameCaller( 1, "Unknown RunInits() option, $_" );
348             }
349             }
350 12         40 local( @Predicates )= @_;
351 12         44 my %have= _rankConds( @_ );
352 12         893 my $inits= 0+@Inits;
353              
354 12         17 my $fail;
355 17 50       100 @Inits= grep {
356 12         46 $fail || _skipInit( $_, \%have, \$fail );
357             } @Inits;
358 12 50       42 die $fail
359             if $fail;
360              
361 12         46 return $inits;
362             }
363              
364              
365             __END__