File Coverage

blib/lib/Stacktrace/Configurable.pm
Criterion Covered Total %
statement 161 161 100.0
branch 94 100 94.0
condition 47 60 78.3
subroutine 24 24 100.0
pod 13 14 92.8
total 339 359 94.4


line stmt bran cond sub pod time code
1             package Stacktrace::Configurable;
2              
3 13     13   282533 use strict;
  13         34  
  13         510  
4 13     13   390 use 5.01;
  13         48  
  13         801  
5             our $VERSION = '0.06';
6              
7 13     13   8052 use Stacktrace::Configurable::Frame;
  13         61  
  13         388  
8              
9 13     13   76 use Scalar::Util qw/looks_like_number/;
  13         89  
  13         1779  
10 13     13   14255 use Data::Dumper ();
  13         154804  
  13         386  
11 13     13   108 no warnings 'uninitialized'; ## no critic
  13         27  
  13         1095  
12              
13             our @attr;
14              
15             BEGIN {
16 13     13   139 @attr=(qw/format frames/);
17 13         45 for (@attr) {
18 26         44 my $attr=$_;
19 13     13   74 no strict 'refs';
  13         21  
  13         1251  
20 26         33917 *{__PACKAGE__.'::'.$attr}=sub : lvalue {
21 72     72   27445 my $I=$_[0];
22 72 100       272 $I->{$attr}=$_[1] if @_>1;
23 72         372 $I->{$attr};
24 26         111 };
25             }
26             }
27              
28 53     53 1 82 sub skip_package_re {}
29 68     68 1 221 sub skip_package_number {1}
30              
31             sub default_format {
32 9     9 1 108 ('env=STACKTRACE_CONFIG,'.
33             '%[nr=1,s= ==== START STACK TRACE ===]b%[nr=1,n]b'.
34             '%4b[%*n] at %f line %l%[n]b'.
35             '%12b%[skip_package]s %[env=STACKTRACE_CONFIG_A]a'.
36             '%[nr!STACKTRACE_CONFIG_MAX,c=%n ... %C frames cut off]b'.
37             '%[nr=$,n]b%[nr=$,s= === END STACK TRACE ===]b%[nr=$,n]b');
38             }
39              
40             sub get_trace {
41 68     68 1 10880 my $I=shift;
42              
43 68         229 my $i=$I->skip_package_number;
44 68         202 my $skip_re=$I->skip_package_re;
45              
46 68         89 my $nr=1;
47              
48 68         84 my @trace;
49 68         92 while (my @l=do {
50             package
51             DB;
52 351         505 @DB::args=();
53 351         2634 CORE::caller $i++;
54             }) {
55 283 100 100     1413 next if !@trace and $skip_re and $l[0]=~$skip_re;
      100        
56 223         1021 push @trace, Stacktrace::Configurable::Frame->new(@l, $nr++,
57             [@DB::args]);
58             }
59              
60 68         140 $I->{frames}=\@trace;
61 68         1294 return $I;
62             }
63              
64             sub new {
65 34     34 0 196 my $class = shift;
66 34   33     177 $class = ref($class)||$class;
67              
68 34         103 my $I = bless {}=>$class;
69 34         144 for (my $i = 0; $i<@_; $ i+= 2) {
70 25         35 my $m = $_[$i];
71 25         93 $I->$m($_[$i+1]);
72             }
73              
74 34   66     246 $I->{format} ||= $I->default_format;
75              
76 34         166 return $I;
77             }
78              
79             sub _use_dumper {
80 70     70   89 my $p = $_[0];
81 70 100       256 return 0 if looks_like_number $_;
82 69 100 66     583 ref and return $p->{dump} || $p->{pkg_dump}->{ref()} || do {
83             my $arg = $_;
84             !!map({ref($arg) =~ /$_/} @{$p->{pkg_dump_re}});
85             };
86 14         108 return 1;
87             }
88              
89             sub fmt_b { # space & control
90 369     369 1 1326 my ($I, $frame, $width, $param) = @_;
91 369         570 my $nr = $frame->{nr};
92 369   100     1426 $width //= 1;
93 369         393 my $cutoff;
94 369 100       1973 if ($param =~ s/^nr!(\d+),//) {
    100          
    100          
    100          
95 6 100       576 return '' unless $nr == $1;
96 2         4 $cutoff = @{$I->{frames}} - $nr;
  2         5  
97 2         4 $#{$I->{frames}} = $nr - 1;
  2         211  
98             } elsif ($param =~ s/^nr!(\w+),//) {
99 35 100 100     285 return '' unless length $ENV{$1} and $nr == $ENV{$1};
100 2         4 $cutoff = @{$I->{frames}} - $nr;
  2         4  
101 2         5 $#{$I->{frames}} = $nr - 1;
  2         57  
102             } elsif ($param =~ s/^nr%(\d+)(?:=(\d+))?,//) {
103 12 100 100     90 return '' unless $nr % $1 == ($2//0);
104             } elsif ($param =~ s/^nr=(\d+|\$),//) {
105 199 100       428 if ($1 eq '$') {
106 85 100       97 return '' unless $nr == @{$I->{frames}};
  85         399  
107             } else {
108 114 100       560 return '' unless $nr == $1;
109             }
110             }
111              
112 206 100       608 if ($param =~ s/^c=//) {
    100          
113 2 50       7 return '' if $cutoff <= 0;
114 2 100       7 $param =~ s/%([Cn])/$1 eq 'C' ? $cutoff : "\n"/ge;
  4         17  
115 2         13 return $param x $width;
116             } elsif ($param =~ s/^s=//) {
117 41         506 return $param x $width;
118             } else {
119 163 100       1075 return +($param eq 'n'
    100          
120             ? "\n"
121             : $param eq 't'
122             ? "\t"
123             : ' ') x $width;
124             }
125             }
126              
127             sub fmt_n { # frame number
128 107     107 1 225 my ($I, $frame, $width, $param) = @_;
129 107 100       260 if ($width eq '*') {
    100          
130 45         50 $width = length '' . (0 + @{$I->{frames}});
  45         111  
131             } elsif ($width eq '-*') {
132 10         11 $width = -length '' . (0 + @{$I->{frames}});
  10         26  
133             }
134 107         610 return sprintf "%${width}d", $frame->{nr};
135             }
136              
137             sub fmt_s { # subroutine
138 60     60 1 144 my ($I, $frame, $width, $param) = @_;
139 60 100       168 if (my $eval = $frame->{evaltext}) {
140 2 50       5 return "require $eval" if $frame->{is_require};
141 2         4 $eval =~ s/([\\\'])/\\$1/g;
142 2         8 return "eval '$eval'";
143             }
144 58         89 my $s = $frame->{subroutine};
145              
146 58         159 for (split /,\s*/, $param) {
147 46 100 66     420 last if s/^skip_package// and $s =~ s!^.*::!!;
148             }
149 58         289 return $s;
150             }
151              
152             sub fmt_a { # args
153 46     46 1 107 my ($I, $frame, $width, $param) = @_;
154 46 50       114 return '' unless $frame->{hasargs};
155 46         129 my @param = split /,\s*/, $param;
156 46         83 my %p;
157             my @ml;
158 46         84 for (@param) {
159             ## no critic
160 56 100       141 $p{dump} = 1, next if /^dump$/;
161 53 100       150 $p{pkg_dump}->{$1} = 1, next if m~^dump=(?!/)(.+)$~;
162 48 100       107 push(@{$p{pkg_dump_re}}, $1), next if m~^dump=/(.+)/$~;
  1         5  
163 47 100       276 push(@param, split /,\s*/, $ENV{$1}), next if /^env=(.+)/;
164 8 100       23 $p{deparse} = 1, next if /^deparse$/;
165 5 50 100     65 @ml = ($1||4, $2||4), next if m~^multiline(?:=(\d+)(?:\.(\d+))?)?$~;
      100        
166             }
167 46 100       112 if (@ml) {
168 15 100 50     1037 return "(\n".join(",\n", map {(' 'x($ml[0]+$ml[1])).$_} map {
  15 50       47  
169 5         14 (!defined $_
170             ? "undef"
171             : _use_dumper (\%p)
172             ? Data::Dumper->new([$_])->Useqq(1)->Deparse($p{deparse} || 0)
173             ->Indent(0)->Terse(1)->Dump
174             : "$_");
175 5         10 } @{$frame->{args}})."\n".(' 'x$ml[0]).")";
176             } else {
177 56 100 100     2513 return '('.join(', ', map {
    100          
178 41         219 (!defined $_
179             ? "undef"
180             : _use_dumper (\%p)
181             ? Data::Dumper->new([$_])->Useqq(1)->Deparse($p{deparse} || 0)
182             ->Indent(0)->Terse(1)->Dump
183             : "$_");
184 41         63 } @{$frame->{args}}).')';
185             }
186             }
187              
188             sub fmt_f { # filename
189 55     55 1 102 my ($I, $frame, $width, $param) = @_;
190 55         95 my $fn = $frame->{filename};
191 55         170 for (split /,\s*/, $param) {
192 16 100 100     118 last if s/^skip_prefix=// and $fn =~ s!^\Q$_\E!!;
193 13 100 100     117 last if s/^basename$// and $fn =~ s!^.*/!!;
194             }
195 55 100 66     178 return substr($fn, 0, $width) . '...' if $width > 0 and length $fn > $width;
196 53 100 66     134 return '...' . substr($fn, $width) if $width < 0 and length $fn > -$width;
197 51         245 return $fn;
198             }
199              
200             sub fmt_l { # linenr
201 49     49 1 98 my ($I, $frame, $width, $param) = @_;
202 49         924 return sprintf "%${width}d", $frame->{line};
203             }
204              
205             sub fmt_c { # context (void/scalar/list)
206 4     4 1 10 my ($I, $frame, $width, $param) = @_;
207 4 100       21 return (!defined $frame->{wantarray}
    100          
208             ? 'void'
209             : $frame->{wantarray}
210             ? 'list'
211             : 'scalar');
212             }
213              
214             sub fmt_p { # package
215 8     8 1 18 my ($I, $frame, $width, $param) = @_;
216 8         10 my $pn = $frame->{package};
217 8         23 for (split /,\s*/, $param) {
218 2 100 66     41 last if s/^skip_prefix=// and $pn =~ s!^\Q$_\E!!;
219             }
220 8 100 66     36 return substr($pn, 0, $width) . '...' if $width > 0 and length $pn > $width;
221 6 100 66     26 return '...' . substr($pn, $width) if $width < 0 and length $pn > -$width;
222 4         13 return $pn;
223             }
224              
225             sub as_string {
226 67     67 1 98 my $I = shift;
227 67         127 my $fmt = $I->{format};
228              
229 67         88 my %seen;
230 67         287 while ($fmt =~ s/^env=(\w+)(,|$)//) {
231 25         68 my $var = $1;
232 25 100       142 return '' if $ENV{$var}=~/^(?:off|no|0)$/i;
233              
234 19         39 undef $seen{$var};
235 19 100       78 unless (length $fmt) {
236 4   33     24 $fmt = $ENV{$var} || $I->default_format;
237 4 100 100     50 $fmt =~ /^env=(\w+)(,|$)/ and exists $seen{$1} and
238             $fmt = 'format cycle detected';
239             }
240             }
241              
242 61         76 local $@;
243 61         413 local $SIG{__DIE__};
244              
245 61         99 my $s = '';
246 61         98 for my $frame (@{$I->{frames}}) {
  61         148  
247 194         288 my $l = $fmt;
248 194         1454 $l =~ s/
249             % # leading %
250             (?:
251             (%)
252             |
253             (-?(?:\d+|\*))? # width
254             (?:\[(.+?)\])? # modifiers
255             ([bnasflcp]) # placeholder
256             )
257 698 50       2757 /$1 ? $1 : do {my $m="fmt_$4"; $I->$m($frame, $2, $3)}/gex;
  698         1214  
  698         1879  
258 194         2066 $s .= $l."\n";
259             }
260 61         161 chomp $s;
261              
262 61         554 return $s;
263             }
264              
265             1;
266             __END__