| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Data::Debug; | 
| 2 |  |  |  |  |  |  |  | 
| 3 |  |  |  |  |  |  | # ABSTRACT: allows for basic data dumping and introspection. | 
| 4 |  |  |  |  |  |  |  | 
| 5 |  |  |  |  |  |  | ####----------------------------------------------------------------### | 
| 6 |  |  |  |  |  |  | ##  Copyright 2014 - Bluehost                                         # | 
| 7 |  |  |  |  |  |  | ##  Distributed under the Perl Artistic License without warranty      # | 
| 8 |  |  |  |  |  |  | ####----------------------------------------------------------------### | 
| 9 |  |  |  |  |  |  |  | 
| 10 | 1 |  |  | 1 |  | 21236 | use strict; | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 27 |  | 
| 11 | 1 |  |  | 1 |  | 6 | use base qw(Exporter); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 684 |  | 
| 12 |  |  |  |  |  |  | our @EXPORT    = qw(debug debug_warn); | 
| 13 |  |  |  |  |  |  | our @EXPORT_OK = qw(debug_text debug_html debug_plain caller_trace); | 
| 14 |  |  |  |  |  |  | our $QR_TRACE1 = qr{ \A (?: /[^/]+ | \.)* / (?: perl | lib | cgi(?:-bin)? ) / (.+) \Z }x; | 
| 15 |  |  |  |  |  |  | our $QR_TRACE2 = qr{ \A .+ / ( [\w\.\-]+ / [\w\.\-]+ ) \Z }x; | 
| 16 |  |  |  |  |  |  |  | 
| 17 |  |  |  |  |  |  | our $VERSION = '0.04'; | 
| 18 |  |  |  |  |  |  |  | 
| 19 | 0 |  |  |  |  | 0 | BEGIN { | 
| 20 |  |  |  |  |  |  | ### cache mod_perl version (light if or if not mod_perl) | 
| 21 |  |  |  |  |  |  | my $v = (! $ENV{'MOD_PERL'}) ? 0 | 
| 22 |  |  |  |  |  |  | # mod_perl/1.27 or mod_perl/1.99_16 or mod_perl/2.0.1 | 
| 23 |  |  |  |  |  |  | # if MOD_PERL is set - don't die if regex fails - just assume 1.0 | 
| 24 | 1 | 0 |  | 1 |  | 5 | : ($ENV{'MOD_PERL'} =~ m{ ^ mod_perl / (\d+\.[\d_]+) (?: \.\d+)? $ }x) ? $1 | 
|  |  | 50 |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | : '1.0_0'; | 
| 26 | 0 |  |  | 0 |  | 0 | sub _mod_perl_version () { $v } | 
| 27 | 1 | 50 |  | 1 |  | 15 | sub _is_mod_perl_1    () { $v <  1.98 && $v > 0 } | 
| 28 | 1 |  |  | 1 |  | 14 | sub _is_mod_perl_2    () { $v >= 1.98 } | 
| 29 |  |  |  |  |  |  |  | 
| 30 |  |  |  |  |  |  | ### cache apache request getter (light if or if not mod_perl) | 
| 31 | 1 |  |  |  |  | 2 | my $sub; | 
| 32 | 1 | 50 |  |  |  | 3 | if (_is_mod_perl_1) { # old mod_perl | 
|  |  | 50 |  |  |  |  |  | 
| 33 | 0 |  |  |  |  | 0 | require Apache; | 
| 34 | 0 |  |  |  |  | 0 | $sub = sub { Apache->request }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 35 |  |  |  |  |  |  | } elsif (_is_mod_perl_2) { | 
| 36 | 0 | 0 |  |  |  | 0 | if (eval { require Apache2::RequestRec }) { # debian style | 
|  | 0 |  |  |  |  | 0 |  | 
| 37 | 0 |  |  |  |  | 0 | require Apache2::RequestUtil; | 
| 38 | 0 |  |  |  |  | 0 | $sub = sub { Apache2::RequestUtil->request }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 39 |  |  |  |  |  |  | } else { # fedora and mandrake style | 
| 40 | 0 |  |  |  |  | 0 | require Apache::RequestUtil; | 
| 41 | 0 |  |  |  |  | 0 | $sub = sub { Apache->request }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 42 |  |  |  |  |  |  | } | 
| 43 |  |  |  |  |  |  | } else { | 
| 44 | 1 |  |  |  |  | 1957 | $sub = sub {}; | 
| 45 |  |  |  |  |  |  | } | 
| 46 | 0 |  |  | 0 | 1 |  | sub apache_request_sub () { $sub } | 
| 47 |  |  |  |  |  |  | } | 
| 48 |  |  |  |  |  |  |  | 
| 49 |  |  |  |  |  |  | my %LINE_CACHE; | 
| 50 |  |  |  |  |  |  | my $DEPARSE; | 
| 51 |  |  |  |  |  |  |  | 
| 52 | 0 |  |  | 0 | 1 |  | sub set_deparse { $DEPARSE = 1 } | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | sub _dump { | 
| 55 | 0 |  | 0 | 0 |  |  | local $Data::Dumper::Deparse   = $DEPARSE && eval {require B::Deparse}; | 
| 56 | 0 |  |  |  |  |  | local $Data::Dumper::Sortkeys  = 1; | 
| 57 | 0 |  |  |  |  |  | local $Data::Dumper::Useqq     = 1; | 
| 58 | 0 |  |  |  |  |  | local $Data::Dumper::Quotekeys = 0; | 
| 59 |  |  |  |  |  |  |  | 
| 60 | 0 |  |  |  |  |  | my $ref; | 
| 61 | 0 | 0 | 0 |  |  |  | for (ref($_[0]) eq 'ARRAY' ? @{ $_[0] } : @_) { last if UNIVERSAL::isa($_, 'HASH') && ($ref = $_->{'dbh_cache'}) } | 
|  | 0 | 0 |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 62 | 0 | 0 |  |  |  |  | local @$ref{keys %$ref} = ('hidden')x(keys %$ref) if $ref; | 
| 63 |  |  |  |  |  |  |  | 
| 64 | 0 |  |  |  |  |  | return Data::Dumper->Dumpperl(\@_); | 
| 65 |  |  |  |  |  |  | } | 
| 66 |  |  |  |  |  |  |  | 
| 67 |  |  |  |  |  |  | ###----------------------------------------------------------------### | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | sub _what_is_this { | 
| 70 | 0 |  |  | 0 |  |  | my ($pkg, $file, $line_n, $called) = caller(1); | 
| 71 | 0 |  |  |  |  |  | $called =~ s/.+:://; | 
| 72 |  |  |  |  |  |  |  | 
| 73 | 0 |  |  |  |  |  | my $line = ''; | 
| 74 | 0 | 0 |  |  |  |  | if (defined $LINE_CACHE{"$file:$line_n"}) { | 
| 75 |  |  |  |  |  |  | # Just use global cache | 
| 76 | 0 |  |  |  |  |  | $line = $LINE_CACHE{"$file:$line_n"}; | 
| 77 |  |  |  |  |  |  | } | 
| 78 |  |  |  |  |  |  | else { | 
| 79 | 0 | 0 |  |  |  |  | if (open my $fh, '<', $file) { | 
| 80 | 0 |  |  |  |  |  | my $n = 0; | 
| 81 | 0 |  |  |  |  |  | my $ignore_after = $line_n + 1000; | 
| 82 | 0 |  |  |  |  |  | while (defined(my $l = <$fh>)) { | 
| 83 | 0 | 0 |  |  |  |  | if (++$n == $line_n) { | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 84 | 0 |  |  |  |  |  | $LINE_CACHE{"$file:$line_n"} = $line = $l; | 
| 85 |  |  |  |  |  |  | } | 
| 86 |  |  |  |  |  |  | elsif ($l =~ /debug/) { | 
| 87 | 0 |  |  |  |  |  | $LINE_CACHE{"$file:$n"} = $l; | 
| 88 |  |  |  |  |  |  | } | 
| 89 |  |  |  |  |  |  | elsif ($n > $ignore_after) { | 
| 90 | 0 |  |  |  |  |  | last; | 
| 91 |  |  |  |  |  |  | } | 
| 92 |  |  |  |  |  |  | } | 
| 93 | 0 |  |  |  |  |  | close $fh; | 
| 94 |  |  |  |  |  |  | } | 
| 95 | 0 |  | 0 |  |  |  | $line ||= ""; | 
| 96 | 0 |  |  |  |  |  | $LINE_CACHE{"$file:$line_n"} = $line; | 
| 97 |  |  |  |  |  |  | } | 
| 98 |  |  |  |  |  |  |  | 
| 99 | 0 | 0 |  |  |  |  | $file =~ s/$QR_TRACE1/$1/ || $file =~ s/$QR_TRACE2/$1/; # trim up extended filename | 
| 100 |  |  |  |  |  |  |  | 
| 101 | 0 |  |  |  |  |  | require Data::Dumper; | 
| 102 | 0 | 0 |  |  |  |  | local $Data::Dumper::Indent = 1 if $called eq 'debug_warn'; | 
| 103 |  |  |  |  |  |  |  | 
| 104 |  |  |  |  |  |  | # dump it out | 
| 105 | 0 |  |  |  |  |  | my @dump = map {_dump($_)} @_; | 
|  | 0 |  |  |  |  |  |  | 
| 106 | 0 |  |  |  |  |  | my @var  = ('$VAR') x @dump; | 
| 107 | 0 |  |  |  |  |  | my $hold; | 
| 108 | 0 | 0 | 0 |  |  |  | if ($line =~ s/^ .*\b \Q$called\E ( \s* \( \s* | \s+ )//x | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 109 |  |  |  |  |  |  | && ($hold = $1) | 
| 110 |  |  |  |  |  |  | && ($line =~ s/ \s* \b if \b .* \n? $ //x | 
| 111 |  |  |  |  |  |  | || $line =~ s/ \s* ; \s* $ //x | 
| 112 |  |  |  |  |  |  | || $line =~ s/ \s+ $ //x)) { | 
| 113 | 0 | 0 |  |  |  |  | $line =~ s/ \s*\) $ //x if $hold =~ /^\s*\(/; | 
| 114 | 0 | 0 |  |  |  |  | my @_var = map {/^[\"\']/ ? 'String' : $_} split (/\s*,\s*/, $line); | 
|  | 0 |  |  |  |  |  |  | 
| 115 | 0 | 0 |  |  |  |  | @var = @_var if $#var == $#_var; | 
| 116 |  |  |  |  |  |  | } | 
| 117 |  |  |  |  |  |  |  | 
| 118 |  |  |  |  |  |  | # spit it out | 
| 119 | 0 | 0 | 0 |  |  |  | if ($called eq 'debug_html' | 
|  |  |  | 0 |  |  |  |  | 
| 120 |  |  |  |  |  |  | || ($called eq 'debug' && $ENV{'REQUEST_METHOD'})) { | 
| 121 | 0 |  |  |  |  |  | my $html = " $called: $file line $line_n\n";  | 
| 122 | 0 |  |  |  |  |  | for (0 .. $#dump) { | 
| 123 | 0 |  |  |  |  |  | $dump[$_] =~ s/(? | 
| 124 | 0 |  |  |  |  |  | $dump[$_] = _html_quote($dump[$_]); | 
| 125 | 0 |  |  |  |  |  | $dump[$_] =~ s|\$VAR1|$var[$_]|g; | 
| 126 | 0 |  |  |  |  |  | $html .= $dump[$_]; | 
| 127 |  |  |  |  |  |  | } | 
| 128 | 0 |  |  |  |  |  | $html .= "\n"; | 
| 129 | 0 | 0 |  |  |  |  | return $html if $called eq 'debug_html'; | 
| 130 | 0 |  |  |  |  |  | my $typed = content_typed(); | 
| 131 | 0 |  |  |  |  |  | print_content_type(); | 
| 132 | 0 | 0 |  |  |  |  | print $typed ? $html : "$html"; | 
| 133 |  |  |  |  |  |  | } else { | 
| 134 | 0 |  |  |  |  |  | my $txt = "$called: $file line $line_n\n"; | 
| 135 | 0 |  |  |  |  |  | for (0 .. $#dump) { | 
| 136 | 0 |  |  |  |  |  | $dump[$_] =~ s|\$VAR1|$var[$_]|g; | 
| 137 | 0 |  |  |  |  |  | $txt .= $dump[$_]; | 
| 138 |  |  |  |  |  |  | } | 
| 139 | 0 |  |  |  |  |  | $txt =~ s/\s*$/\n/; | 
| 140 | 0 | 0 |  |  |  |  | return $txt if $called eq 'debug_text'; | 
| 141 |  |  |  |  |  |  |  | 
| 142 | 0 | 0 |  |  |  |  | if ($called eq 'debug_warn') { | 
| 143 | 0 |  |  |  |  |  | warn $txt; | 
| 144 |  |  |  |  |  |  | } | 
| 145 |  |  |  |  |  |  | else { | 
| 146 | 0 |  |  |  |  |  | print $txt; | 
| 147 |  |  |  |  |  |  | } | 
| 148 |  |  |  |  |  |  | } | 
| 149 | 0 |  |  |  |  |  | return @_[0..$#_]; | 
| 150 |  |  |  |  |  |  | } | 
| 151 |  |  |  |  |  |  |  | 
| 152 | 0 |  |  | 0 | 1 |  | sub debug      { &_what_is_this } | 
| 153 | 0 |  |  | 0 | 1 |  | sub debug_warn { &_what_is_this } | 
| 154 | 0 |  |  | 0 | 1 |  | sub debug_text { &_what_is_this } | 
| 155 | 0 |  |  | 0 | 1 |  | sub debug_html { &_what_is_this } | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | sub debug_plain { | 
| 158 | 0 |  |  | 0 | 1 |  | require Data::Dumper; | 
| 159 | 0 |  |  |  |  |  | local $Data::Dumper::Indent = 1; | 
| 160 | 0 |  |  |  |  |  | local $Data::Dumper::Terse = 1; | 
| 161 | 0 |  |  |  |  |  | my $dump = join "\n", map {_dump($_)} @_; | 
|  | 0 |  |  |  |  |  |  | 
| 162 | 0 | 0 |  |  |  |  | print $dump if !defined wantarray; | 
| 163 | 0 |  |  |  |  |  | return $dump; | 
| 164 |  |  |  |  |  |  | } | 
| 165 |  |  |  |  |  |  |  | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  | sub content_typed { | 
| 168 | 0 | 0 |  | 0 | 1 |  | if (my $r = apache_request_sub()->()) { | 
| 169 | 0 |  |  |  |  |  | return $r->bytes_sent; | 
| 170 |  |  |  |  |  |  | } else { | 
| 171 | 0 | 0 |  |  |  |  | return $ENV{'CONTENT_TYPED'} ? 1 : undef; | 
| 172 |  |  |  |  |  |  | } | 
| 173 |  |  |  |  |  |  | } | 
| 174 |  |  |  |  |  |  |  | 
| 175 |  |  |  |  |  |  | sub print_content_type { | 
| 176 | 0 |  |  | 0 | 1 |  | my $type = "text/html"; | 
| 177 |  |  |  |  |  |  |  | 
| 178 | 0 | 0 |  |  |  |  | if (my $r = apache_request_sub()->()) { | 
| 179 | 0 | 0 |  |  |  |  | return if $r->bytes_sent; | 
| 180 | 0 |  |  |  |  |  | $r->content_type($type); | 
| 181 | 0 | 0 |  |  |  |  | $r->send_http_header if _is_mod_perl_1; | 
| 182 |  |  |  |  |  |  | } else { | 
| 183 | 0 | 0 |  |  |  |  | if (! $ENV{'CONTENT_TYPED'}) { | 
| 184 | 0 |  |  |  |  |  | print "Content-Type: $type\r\n\r\n"; | 
| 185 | 0 |  |  |  |  |  | $ENV{'CONTENT_TYPED'} = ''; | 
| 186 |  |  |  |  |  |  | } | 
| 187 | 0 |  |  |  |  |  | $ENV{'CONTENT_TYPED'} .= sprintf("%s, %d\n", (caller)[1,2]); | 
| 188 |  |  |  |  |  |  | } | 
| 189 |  |  |  |  |  |  | } | 
| 190 |  |  |  |  |  |  |  | 
| 191 |  |  |  |  |  |  | sub _html_quote { | 
| 192 | 0 |  |  | 0 |  |  | my $value = shift; | 
| 193 | 0 | 0 |  |  |  |  | return '' if ! defined $value; | 
| 194 | 0 |  |  |  |  |  | $value =~ s/&/&/g; | 
| 195 | 0 |  |  |  |  |  | $value =~ s/</g; | 
| 196 | 0 |  |  |  |  |  | $value =~ s/>/>/g; | 
| 197 | 0 |  |  |  |  |  | return $value; | 
| 198 |  |  |  |  |  |  | } | 
| 199 |  |  |  |  |  |  |  | 
| 200 |  |  |  |  |  |  | sub caller_trace { | 
| 201 | 0 | 0 |  | 0 | 1 |  | eval { require 5.8.0 } || return ['Caller trace requires perl 5.8']; | 
|  | 0 |  |  |  |  |  |  | 
| 202 | 0 |  |  |  |  |  | require Carp::Heavy; | 
| 203 | 0 |  |  |  |  |  | local $Carp::MaxArgNums = 5; | 
| 204 | 0 |  |  |  |  |  | local $Carp::MaxArgLen  = 20; | 
| 205 | 0 |  | 0 |  |  |  | my $i    = shift || 0; | 
| 206 | 0 |  | 0 |  |  |  | my $skip = shift || {}; | 
| 207 | 0 |  |  |  |  |  | my @i = (); | 
| 208 | 0 |  |  |  |  |  | my $max1 = 0; | 
| 209 | 0 |  |  |  |  |  | my $max2 = 0; | 
| 210 | 0 |  |  |  |  |  | my $max3 = 0; | 
| 211 | 0 |  |  |  |  |  | while (my %i = Carp::caller_info(++$i)) { | 
| 212 | 0 | 0 |  |  |  |  | next if $skip->{$i{file}}; | 
| 213 | 0 |  |  |  |  |  | $i{sub_name} =~ s/\((.*)\)$//; | 
| 214 | 0 | 0 |  |  |  |  | $i{args} = $i{has_args} ? $1 : ""; | 
| 215 | 0 |  |  |  |  |  | $i{sub_name} =~ s/^.*?([^:]+)$/$1/; | 
| 216 | 0 | 0 |  |  |  |  | $i{file} =~ s/$QR_TRACE1/$1/ || $i{file} =~ s/$QR_TRACE2/$1/; | 
| 217 | 0 | 0 |  |  |  |  | $max1 = length($i{sub_name}) if length($i{sub_name}) > $max1; | 
| 218 | 0 | 0 |  |  |  |  | $max2 = length($i{file})     if length($i{file})     > $max2; | 
| 219 | 0 | 0 |  |  |  |  | $max3 = length($i{line})     if length($i{line})     > $max3; | 
| 220 | 0 |  |  |  |  |  | push @i, \%i; | 
| 221 |  |  |  |  |  |  | } | 
| 222 | 0 |  |  |  |  |  | foreach my $ref (@i) { | 
| 223 |  |  |  |  |  |  | $ref = sprintf("%-${max1}s at %-${max2}s line %${max3}s", $ref->{sub_name}, $ref->{file}, $ref->{line}) | 
| 224 | 0 | 0 |  |  |  |  | . ($ref->{args} ? " ($ref->{args})" : ""); | 
| 225 |  |  |  |  |  |  | } | 
| 226 | 0 |  |  |  |  |  | return \@i; | 
| 227 |  |  |  |  |  |  | } | 
| 228 |  |  |  |  |  |  |  | 
| 229 |  |  |  |  |  |  | ###----------------------------------------------------------------### | 
| 230 |  |  |  |  |  |  |  | 
| 231 |  |  |  |  |  |  | 1; | 
| 232 |  |  |  |  |  |  |  | 
| 233 |  |  |  |  |  |  | __END__ |