# Jonathan Graehl - "jonathan#graehl!org" =~ tr/#!/@./ =begin twiki ---++ Description Debug routines with custom printed representations depending on argument types. Configurable output by default to STDERR (if the DEBUG environment variable is set) or to HTML (if the SCRIPT_NAME var contains "debug"). For HTML output, prints a default response header unless you have already done so yourself (and indicated it with &webHeadersDone). =cut package WebDebug; use strict; #BEGIN { $diagnostics::PRETTY = 1 } #use diagnostics; use Exporter; use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $WEBDEBUG $SCRIPTURL $BROWSERDEBUG $WRITELNDRIVER $WEBHEADERSDONE $SHELLDEBUG $DEBUGDEFAULTON @WEBBUFFER %DEBUGONPACKAGE %DEBUGTABLE); $VERSION = 1.00; @ISA = qw(Exporter); @EXPORT = qw(); @EXPORT_OK = qw(&debug &debugAlways &debugOn &debugOff &debugging &debugDefaultOn &debugDefaultOff &debugDump &setDumperFor &webHeadersDone &webBoxedQuote &debugWriteLn &installWriteLnDriver); %EXPORT_TAGS = ( all => \@EXPORT_OK, ); # ========================= =pod ---++ Functions: Debug messages and value inspection =cut =pod ---+++ &init called once on module load, but can be repeated for new web requests in a mod_perl like environment =cut sub init { $SCRIPTURL=exists $ENV{SCRIPT_NAME} ? $ENV{SCRIPT_NAME} : ''; $WEBDEBUG=($SCRIPTURL =~ /debug/); $BROWSERDEBUG=$WEBDEBUG; # && exists $ENV{REMOTE_HOST}; eval("use CGI;use CGI::Carp qw(fatalsToBrowser warningsToBrowser);") if $BROWSERDEBUG; $WRITELNDRIVER=undef; $WEBHEADERSDONE=0; $SHELLDEBUG=exists $ENV{DEBUG} || ($WEBDEBUG && !$BROWSERDEBUG); @WEBBUFFER=(); &debugDefaultOn; %DEBUGONPACKAGE=(); } &initDump; &init; =pod ---+++ &webBoxedQuote($text) returns HTML for displaying boxed blockquoted verbatim (fully escaped) text as it would appear in a plain terminal =cut sub webBoxedQuote { my($message) = @_; my $escapeH=eval '\\&CGI::escapeHTML'; my $esc=defined($escapeH) ? $escapeH->($message) : $message; return <<__EOF;
$esc
__EOF } =pod ---+++ &installWriteLnDriver takes a reference to code to be called with any debug text (even if it is also displayed in HTML or to console) =cut sub installWriteLnDriver { $WRITELNDRIVER=$_[0]; } =pod ---+++ &debugWriteLn calls writeDebug (appends message to data/debug.txt). if the scriptname contains 'debug', then messages are also output as HTML. if the $DEBUG environment var is set and the script is run from the command line the message is output to STDERR as well. also passes through argument to any user supplied WriteLnDriver =cut sub debugWriteLn { if ($BROWSERDEBUG) { my $out=&webBoxedQuote; if ($WEBHEADERSDONE) { print $out; } else { # print &CGI::header(),&CGI::start_html(&CGI::script_name()." - debug mode"); push @WEBBUFFER,$out; } } elsif ($SHELLDEBUG) { print STDERR $_[0],"\n"; } &$WRITELNDRIVER if (defined $WRITELNDRIVER); } =pod ---+++ &webHeadersDone For web browser debug output, must be called after your HTTP headers are printed, or else you won't see anything. =cut sub webHeadersDone { $WEBHEADERSDONE=1; eval("&warningsToBrowser(1);") if $BROWSERDEBUG; print $_ for (@WEBBUFFER); @WEBBUFFER=(); } =pod ---+++ &setDumperFor($argtype,$handler) $argtype is a reference to an object, and $handler is the new &debugDump handler for obtaining printed representations of objects of that type =cut sub setDumperFor { my ($argtype,$handler) = @_; $DEBUGTABLE{$argtype} = $handler; } =pod ---+++ &debugDump($datum) $datum is an object (not a reference to it) - a printed representation of the object's value is returned =cut sub debugDump { my $argtype = ref($_[0]) || ''; my $handler = $DEBUGTABLE{$argtype}; # if (!$handler) { # foreach my $anc ( ancestors($argtype) ) { # $handler = $DEBUGTABLE{$anc}; # next unless $handler; # $DEBUGTABLE{$argtype} = $handler; # last; # } # } return "unknown<$argtype>($_[0])" unless $handler; return $handler->(@_); } sub initDump { %DEBUGTABLE=(); setDumperFor '' => sub { defined($_[0]) ? qq{"$_[0]"} : 'undef' }; setDumperFor "REF" => sub { '\\('.&debugDump(${$_[0]}).')' }; setDumperFor "SCALAR" => sub { '\\('.&debugDump(${$_[0]}).')' }; # => sub { $_[0].'='.&debugDump(${$_[0]}) }; setDumperFor "ARRAY" => sub { my @arrayreps=map {&debugDump($_)} @{$_[0]}; '[' . join(',',@arrayreps) . ']' }; setDumperFor "HASH" => sub { my @entryreps=map {&debugDump($_) . "=>".&debugDump($_[0]->{$_})} keys(%{$_[0]}); '{' . join(',',@entryreps) . '}' }; setDumperFor "CODE" => sub { "$_[0]" }; setDumperFor "GLOB" => sub { "GLOB:$_[0]" }; } =pod ---+++ &debugAlways(...) same as &debug(...) but always active, even if &debugOff was called =cut sub debugAlways { my @args=map { &debugDump($_) } @_; my ($package, $filename, $line) = caller; $filename = $1 if $filename =~ m|/([^/]+)$|; my $dbg="[$package]$filename($line): ".join('; ',@args); &debugWriteLn($dbg); } sub setDebug { my ($package, $filename, $line) = caller; $DEBUGONPACKAGE{$package} = $_[0]; } =pod ---+++ &debugOn enables debug output for the calling package =cut sub debugOn { my ($package, $filename, $line) = caller; $DEBUGONPACKAGE{$package} = 1; } =pod ---+++ &debugOff disables debug output for the calling package =cut sub debugOff { my ($package, $filename, $line) = caller; $DEBUGONPACKAGE{$package} = 0; } =pod ---+++ &debugDefaultOn sets default to &debugOn =cut sub debugDefaultOn { $DEBUGDEFAULTON = 1; } =pod ---+++ &debugDefaultOff sets default to &debugOff =cut sub debugDefaultOff { $DEBUGDEFAULTON = 0; } =pod ---+++ &debugging($package) returns true if debug output is enabled in the package (defaults to calling package) (else returns false, duh) =cut sub debugging { my ($package, $filename, $line) = caller; $package = $_[0] if defined($_[0]); return (exists $DEBUGONPACKAGE{$package}) ? $DEBUGONPACKAGE{$package} : $DEBUGDEFAULTON; } =pod ---+++ &debug(...) dumps the values of the arguments to the debug display (debugWriteLn) if &debugOn was called =cut sub debug { my ($package, $filename, $line) = caller; if (&debugging($package)) { my @args=map { &debugDump($_) } @_; $filename = $1 if $filename =~ m|/([^/]+)$|; my $dbg="[$package]$filename($line): ".join('; ',@args); &debugWriteLn($dbg); } } 1;