File: | local/lib/perl5/Sub/Uplevel.pm |
Coverage: | 38.9% |
line | stmt | bran | cond | sub | time | code |
---|---|---|---|---|---|---|
1 | package Sub::Uplevel; | |||||
2 | 6 6 | 49 10 | use 5.006; | |||
3 | 6 6 6 | 8 5 160 | use strict; | |||
4 | # ABSTRACT: apparently run a function in a higher stack frame | |||||
5 | our $VERSION = '0.24'; # VERSION | |||||
6 | ||||||
7 | # Frame check global constant | |||||
8 | our $CHECK_FRAMES; | |||||
9 | BEGIN { | |||||
10 | 6 | 55 | $CHECK_FRAMES = !! $CHECK_FRAMES; | |||
11 | } | |||||
12 | 6 6 6 | 10 4 338 | use constant CHECK_FRAMES => $CHECK_FRAMES; | |||
13 | ||||||
14 | # We must override *CORE::GLOBAL::caller if it hasn't already been | |||||
15 | # overridden or else Perl won't see our local override later. | |||||
16 | ||||||
17 | if ( not defined *CORE::GLOBAL::caller{CODE} ) { | |||||
18 | *CORE::GLOBAL::caller = \&_normal_caller; | |||||
19 | } | |||||
20 | ||||||
21 | # modules to force reload if ":aggressive" is specified | |||||
22 | my @reload_list = qw/Exporter Exporter::Heavy/; | |||||
23 | ||||||
24 | sub import { | |||||
25 | 6 6 6 | 13 2 426 | no strict 'refs'; ## no critic | |||
26 | 12 | 12 | my ($class, @args) = @_; | |||
27 | 12 | 13 | for my $tag ( @args, 'uplevel' ) { | |||
28 | 18 | 18 | if ( $tag eq 'uplevel' ) { | |||
29 | 18 | 16 | my $caller = caller(0); | |||
30 | 18 18 | 11 44 | *{"$caller\::uplevel"} = \&uplevel; | |||
31 | } | |||||
32 | elsif( $tag eq ':aggressive' ) { | |||||
33 | 0 | 0 | _force_reload( @reload_list ); | |||
34 | } | |||||
35 | else { | |||||
36 | 0 | 0 | die qq{"$tag" is not exported by the $class module\n} | |||
37 | } | |||||
38 | } | |||||
39 | 12 | 358 | return; | |||
40 | } | |||||
41 | ||||||
42 | sub _force_reload { | |||||
43 | 6 6 6 | 13 4 1565 | no warnings 'redefine'; | |||
44 | 0 | 0 | local $^W = 0; | |||
45 | 0 | 0 | for my $m ( @_ ) { | |||
46 | 0 | 0 | $m =~ s{::}{/}g; | |||
47 | 0 | 0 | $m .= ".pm"; | |||
48 | 0 | 0 | require $m if delete $INC{$m}; | |||
49 | } | |||||
50 | } | |||||
51 | ||||||
52 | ||||||
53 | # @Up_Frames -- uplevel stack | |||||
54 | # $Caller_Proxy -- whatever caller() override was in effect before uplevel | |||||
55 | our (@Up_Frames, $Caller_Proxy); | |||||
56 | ||||||
57 | sub _apparent_stack_height { | |||||
58 | 0 | 0 | my $height = 1; # start above this function | |||
59 | 0 | 0 | while ( 1 ) { | |||
60 | 0 | 0 | last if ! defined scalar $Caller_Proxy->($height); | |||
61 | 0 | 0 | $height++; | |||
62 | } | |||||
63 | 0 | 0 | return $height - 1; # subtract 1 for this function | |||
64 | } | |||||
65 | ||||||
66 | sub uplevel { | |||||
67 | # Backwards compatible version of "no warnings 'redefine'" | |||||
68 | 0 | 0 | my $old_W = $^W; | |||
69 | 0 | 0 | $^W = 0; | |||
70 | ||||||
71 | # Update the caller proxy if the uplevel override isn't in effect | |||||
72 | 0 | 0 | local $Caller_Proxy = *CORE::GLOBAL::caller{CODE} | |||
73 | if *CORE::GLOBAL::caller{CODE} != \&_uplevel_caller; | |||||
74 | 0 | 0 | local *CORE::GLOBAL::caller = \&_uplevel_caller; | |||
75 | ||||||
76 | # Restore old warnings state | |||||
77 | 0 | 0 | $^W = $old_W; | |||
78 | ||||||
79 | 0 | 0 | if ( CHECK_FRAMES and $_[0] >= _apparent_stack_height() ) { | |||
80 | require Carp; | |||||
81 | Carp::carp("uplevel $_[0] is more than the caller stack"); | |||||
82 | } | |||||
83 | ||||||
84 | 0 | 0 | local @Up_Frames = (shift, @Up_Frames ); | |||
85 | ||||||
86 | 0 | 0 | my $function = shift; | |||
87 | 0 | 0 | return $function->(@_); | |||
88 | } | |||||
89 | ||||||
90 | sub _normal_caller (;$) { ## no critic Prototypes | |||||
91 | 71 | 44 | my ($height) = @_; | |||
92 | 71 | 47 | $height++; | |||
93 | 71 | 223 | my @caller = CORE::caller($height); | |||
94 | 71 | 107 | if ( CORE::caller() eq 'DB' ) { | |||
95 | # Oops, redo picking up @DB::args | |||||
96 | package DB; | |||||
97 | 0 | 0 | @caller = CORE::caller($height); | |||
98 | } | |||||
99 | ||||||
100 | 71 | 100 | return if ! @caller; # empty | |||
101 | 71 | 129 | return $caller[0] if ! wantarray; # scalar context | |||
102 | 31 | 65 | return @_ ? @caller : @caller[0..2]; # extra info or regular | |||
103 | } | |||||
104 | ||||||
105 | sub _uplevel_caller (;$) { ## no critic Prototypes | |||||
106 | 0 | my $height = $_[0] || 0; | ||||
107 | ||||||
108 | # shortcut if no uplevels have been called | |||||
109 | # always add +1 to CORE::caller (proxy caller function) | |||||
110 | # to skip this function's caller | |||||
111 | 0 | return $Caller_Proxy->( $height + 1 ) if ! @Up_Frames; | ||||
112 | ||||||
113 | ||||||
114 | 0 | my $saw_uplevel = 0; | ||||
115 | 0 | my $adjust = 0; | ||||
116 | ||||||
117 | # walk up the call stack to fight the right package level to return; | |||||
118 | # look one higher than requested for each call to uplevel found | |||||
119 | # and adjust by the amount found in the Up_Frames stack for that call. | |||||
120 | # We *must* use CORE::caller here since we need the real stack not what | |||||
121 | # some other override says the stack looks like, just in case that other | |||||
122 | # override breaks things in some horrible way | |||||
123 | ||||||
124 | 0 | for ( my $up = 0; $up <= $height + $adjust; $up++ ) { | ||||
125 | 0 | my @caller = CORE::caller($up + 1); | ||||
126 | 0 | if( defined $caller[0] && $caller[0] eq __PACKAGE__ ) { | ||||
127 | # add one for each uplevel call seen | |||||
128 | # and look into the uplevel stack for the offset | |||||
129 | 0 | $adjust += 1 + $Up_Frames[$saw_uplevel]; | ||||
130 | 0 | $saw_uplevel++; | ||||
131 | } | |||||
132 | } | |||||
133 | ||||||
134 | # For returning values, we pass through the call to the proxy caller | |||||
135 | # function, just at a higher stack level | |||||
136 | 0 | my @caller = $Caller_Proxy->($height + $adjust + 1); | ||||
137 | 0 | if ( CORE::caller() eq 'DB' ) { | ||||
138 | # Oops, redo picking up @DB::args | |||||
139 | package DB; | |||||
140 | 0 | @caller = $Sub::Uplevel::Caller_Proxy->($height + $adjust + 1); | ||||
141 | } | |||||
142 | ||||||
143 | 0 | return if ! @caller; # empty | ||||
144 | 0 | return $caller[0] if ! wantarray; # scalar context | ||||
145 | 0 | return @_ ? @caller : @caller[0..2]; # extra info or regular | ||||
146 | } | |||||
147 | ||||||
148 | ||||||
149 | 1; | |||||
150 |