--- /dev/null
+# Copyright (c) 2010 Elizabeth Grace Frank-Backman.
+# All rights reserved.
+# Liscenced under the "Artistic Liscence"
+# (see http://dev.perl.org/licenses/artistic.html)
+
+use 5.8.8;
+use strict;
+use warnings;
+use overload;
+
+package Exception::Lite;
+our @ISA = qw(Exporter);
+our @EXPORT_OK=qw(declareExceptionClass isException isChainable
+ onDie onWarn);
+our %EXPORT_TAGS
+ =( common => [qw(declareExceptionClass isException isChainable)]
+ , all => [@EXPORT_OK]
+ );
+my $CLASS='Exception::Lite';
+
+#------------------------------------------------------------------
+
+our $STRINGIFY=3;
+our $FILTER=1;
+our $UNDEF='<undef>';
+our $TAB=3;
+our $LINE_LENGTH=120;
+
+# provide command line control over amount and layout of debugging
+# information, e.g. perl -mException::Lite=STRINGIFY=4
+
+sub import {
+ Exception::Lite->export_to_level(1, grep {
+ if (/^(\w+)=(.*)$/) {
+ my $k = $1;
+ my $v = $2;
+ if ($k eq 'STRINGIFY') { $STRINGIFY=$v;
+ } elsif ($k eq 'FILTER') { $FILTER=$v;
+ } elsif ($k eq 'LINE_LENGTH') { $LINE_LENGTH=$v;
+ } elsif ($k eq 'TAB') { $TAB=$v;
+ }
+ 0;
+ } else {
+ 1;
+ }
+ } @_);
+}
+
+#------------------------------------------------------------------
+# Note to source code divers: DO NOT USE THIS. This is intended for
+# internal use but must be declared with "our" because we need to
+# localize it. This is an implementation detail and cannot be relied
+# on for future releases.
+
+our $STACK_OFFSET=0;
+
+#------------------------------------------------------------------
+
+use Scalar::Util ();
+use constant EVAL => '(eval)';
+
+#==================================================================
+# EXPORTABLE FUNCTIONS
+#==================================================================
+
+sub declareExceptionClass {
+ my ($sClass, $sSuperClass, $xFormatRule, $bCustomizeSubclass) = @_;
+ my $sPath = $sClass; $sPath =~ s/::/\//g; $sPath .= '.pm';
+ if ($INC{$sPath}) {
+ # we want to start with the caller's frame, not ours
+ local $STACK_OFFSET = $STACK_OFFSET + 1;
+ die 'Exception::Lite::Any'->new("declareExceptionClass failed: "
+ . "$sClass is already defined!");
+ return undef;
+ }
+
+ my $sRef=ref($sSuperClass);
+ if ($sRef) {
+ $bCustomizeSubclass = $xFormatRule;
+ $xFormatRule = $sSuperClass;
+ $sSuperClass=undef;
+ } else {
+ $sRef = ref($xFormatRule);
+ if (!$sRef && defined($xFormatRule)) {
+ $bCustomizeSubclass = $xFormatRule;
+ $xFormatRule = undef;
+ }
+ }
+
+ # set up things dependent on whether or not the class has a
+ # format string or expects a message for each instance
+
+ my ($sLeadingParams, $sAddOrOmit, $sRethrowMsg, $sMakeMsg);
+ my $sReplaceMsg='';
+
+ if ($sRef) {
+ $sLeadingParams='my $e; $e=shift if ref($_[0]);';
+ $sAddOrOmit='added an unnecessary message or format';
+ $sRethrowMsg='';
+
+ #generate format rule
+ $xFormatRule=$xFormatRule->($sClass) if ($sRef eq 'CODE');
+
+ my $sFormat= 'q{' . $xFormatRule->[0] . '}';
+ if (scalar($xFormatRule) == 1) {
+ $sMakeMsg='my $msg='.$sFormat;
+ } else {
+ my $sSprintf = 'Exception::Lite::_sprintf(' . $sFormat
+ . ', map {defined($_)?$_:\''. $UNDEF .'\'} @$h{qw('
+ . join(' ', @$xFormatRule[1..$#$xFormatRule]) . ')});';
+ $sMakeMsg='my $msg='.$sSprintf;
+ $sReplaceMsg='$_[0]->[0]='.$sSprintf;
+ }
+
+ } else {
+ $sLeadingParams = 'my $e=shift; my $msg;'.
+ 'if(ref($e)) { $msg=shift; $msg=$e->[0] if !defined($msg);}'.
+ 'else { $msg=$e;$e=undef; }';
+ $sAddOrOmit='omitted a required message';
+ $sRethrowMsg='my $msg=shift; $_[0]->[0]=$msg if defined($msg);';
+ $sMakeMsg='';
+ }
+
+ # put this in an eval so that it doesn't cause parse errors at
+ # compile time in no-threads versions of Perl
+
+ my $sTid = eval q{defined(&threads::tid)?'threads->tid':'undef'};
+
+ my $sDeclare = "package $sClass;".
+ 'sub new { my $cl=shift;'. $sLeadingParams .
+ 'my $st=Exception::Lite::_cacheStackTrace($e);'.
+ 'my $h= Exception::Lite::_shiftProperties($cl' .
+ ',$st,"'.$sAddOrOmit.'",@_);' . $sMakeMsg .
+ 'my $self=bless([$msg,$h,$st,$$,'.$sTid.',$e,[]],$cl);';
+
+ # the remainder depends on the type of subclassing
+
+ if ($bCustomizeSubclass) {
+ $sDeclare .= '$self->[7]={}; $self->_new(); return $self; }'
+ . 'sub _p_getSubclassData { $_[0]->[7]; }';
+ } else {
+ $sDeclare .= 'return $self;}'.
+ 'sub replaceProperties {'.
+ 'my $h={%{$_[0]->[1]},%{$_[1]}}; $_[0]->[1]=$h;'.$sReplaceMsg.
+ '}'.
+ 'sub rethrow {' .
+ 'my $self=shift;' . $sRethrowMsg .
+ 'Exception::Lite::_rethrow($self,"'.$sAddOrOmit.'",@_)' .
+ '}';
+
+ unless (isExceptionClass($sSuperClass)) {
+ $sDeclare .=
+ 'sub _getInterface { \'Exception::Lite\' }' .
+ 'sub getMessage { $_[0]->[0] };' .
+ 'sub getProperty { $_[0]->[1]->{$_[1]} }' .
+ 'sub isProperty { exists($_[0]->[1]->{$_[1]})?1:0 }' .
+ 'sub getStackTrace { $_[0]->[2] }' .
+ 'sub getFrameCount { scalar(@{$_[0]->[2]}); }' .
+ 'sub getFile { $_[0]->[2]->[ $_[1]?$_[1]:0 ]->[0] };' .
+ 'sub getLine { $_[0]->[2]->[ $_[1]?$_[1]:0 ]->[1] };' .
+ 'sub getSubroutine { $_[0]->[2]->[ $_[1]?$_[1]:0 ]->[2] };' .
+ 'sub getArgs { $_[0]->[2]->[ $_[1]?$_[1]:0 ]->[3] };' .
+ 'sub getPackage {$_[0]->[2]->[-1]->[2] =~ /(\w+)>$/;$1}'.
+ 'sub getPid { $_[0]->[3] }' .
+ 'sub getTid { $_[0]->[4] }' .
+ 'sub getChained { $_[0]->[5] }' .
+ 'sub getPropagation { $_[0]->[6]; }' .
+ 'use overload '.
+ 'q{""} => \&Exception::Lite::_dumpMessage ' .
+ ', q{0+} => \&Exception::Lite::_refaddr, fallback=>1;' .
+ 'sub PROPAGATE { push @{$_[0]->[6]},[$_[1],$_[2]]; $_[0]}';
+ }
+ }
+ $sDeclare .= 'return 1;';
+
+ local $SIG{__WARN__} = sub {
+ my ($p,$f,$l) = caller(2);
+ my $s=$_[0]; $s =~ s/at \(eval \d+\)\s+line\s+\d+\.//m;
+ print STDERR "$s in declareExceptionClass($sClass,...) "
+ ."in file $f, line $l\n";
+ };
+
+ eval $sDeclare or do {
+ my ($p,$f,$l) = caller(1);
+ print STDERR "Can't create class $sClass at file $f, line $l\n";
+ if ($sClass =~ /\w:\w/) {
+ print STDERR "Bad class name: "
+ ."At least one ':' is not doubled\n";
+ } elsif ($sClass !~ /^\w+(?:::\w+)*$/) {
+ print STDERR "Bad class name: $sClass\n";
+ } else {
+ $sDeclare=~s/(sub |use )/\n$1/g; print STDERR "$sDeclare\n";
+ }
+ };
+
+ # this needs to be separate from the eval, otherwise it never
+ # ends up in @INC or @ISA, at least in Perl 5.8.8
+ $INC{$sPath} = __FILE__;
+ eval "\@${sClass}::ISA=qw($sSuperClass);" if $sSuperClass;
+
+ return $sClass;
+}
+
+#------------------------------------------------------------------
+
+sub isChainable { return ref($_[0])?1:0; }
+
+#------------------------------------------------------------------
+
+sub isException {
+ my ($e, $sClass) = @_;
+ my $sRef=ref($e);
+ return !defined($sClass)
+ ? ($sRef ? isExceptionClass($sRef) : 0)
+ : $sClass eq ''
+ ? ($sRef eq '' ? 1 : 0)
+ : ($sRef eq '')
+ ? 0
+ : $sRef->isa($sClass)
+ ?1:0;
+}
+
+#------------------------------------------------------------------
+
+sub isExceptionClass {
+ return defined($_[0]) && $_[0]->can('_getInterface')
+ && ($_[0]->_getInterface() eq __PACKAGE__) ? 1 : 0;
+}
+
+#------------------------------------------------------------------
+
+sub onDie {
+ my $iStringify = $_[0];
+ $SIG{__DIE__} = sub {
+ $Exception::Lite::STRINGIFY=$iStringify;
+ warn 'Exception::Lite::Any'->new('Unexpected death:'.$_[0])
+ unless $^S || isException($_[0]);
+ };
+}
+
+#------------------------------------------------------------------
+
+sub onWarn {
+ my $iStringify = $_[0];
+ $SIG{__WARN__} = sub {
+ $Exception::Lite::STRINGIFY=$iStringify;
+ print STDERR 'Exception::Lite::Any'->new("Warning: $_[0]");
+ };
+}
+
+#==================================================================
+# PRIVATE SUBROUTINES
+#==================================================================
+
+#------------------------------------------------------------------
+
+sub _cacheCall {
+ my $iFrame = $_[0];
+
+ my @aCaller;
+ my $aArgs;
+
+ # caller populates @DB::args if called within DB package
+ eval {
+ # this 2 line wierdness is needed to prevent Module::Build from finding
+ # this and adding it to the provides list.
+ package
+ DB;
+
+ #get rid of eval and call to _cacheCall
+ @aCaller = caller($iFrame+2);
+
+ # mark leading undefined elements as maybe shifted away
+ my $iDefined;
+ if ($#aCaller < 0) {
+ @DB::args=@ARGV;
+ }
+ $aArgs = [ map {
+ defined($_)
+ ? do {$iDefined=1;
+ "'$_'" . (overload::Method($_,'""')
+ ? ' ('.overload::StrVal($_).')':'')}
+ : 'undef' . (defined($iDefined)
+ ? '':' (maybe shifted away?)')
+ } @DB::args];
+ };
+
+ return $#aCaller < 0 ? \$aArgs : [ @aCaller[0..3], $aArgs ];
+}
+
+#------------------------------------------------------------------
+
+sub _cacheStackTrace {
+ my $e=$_[0]; my $st=[];
+
+ # set up initial frame
+ my $iFrame= $STACK_OFFSET + 1; # call to new
+ my $aCall = _cacheCall($iFrame++);
+ my ($sPackage, $iFile, $iLine, $sSub, $sArgs) = @$aCall;
+ my $iLineFrame=$iFrame;
+
+ $aCall = _cacheCall($iFrame++); #context of call to new
+ while (ref($aCall) ne 'REF') {
+ $sSub = $aCall->[3]; # subroutine containing file,line
+ $sArgs = $aCall->[4]; # args used to call $sSub
+
+ #print STDERR "debug-2: package=$sPackage file=$iFile line=$iLine"
+ # ." sub=$sSub, args=@$sArgs\n";
+
+ # in evals we want the line number within the eval, but the
+ # name of the sub in which the eval was located. To get this
+ # we wait to push on the stack until we get an actual sub name
+ # and we avoid overwriting the location information, hence 'ne'
+
+ if (!$FILTER || ($sSub ne EVAL)) {
+ my $aFrame=[ $iFile, $iLine, $sSub, $sArgs ];
+ ($sPackage, $iFile, $iLine) = @$aCall;
+ $iLineFrame=$iFrame;
+
+ my $sRef=ref($FILTER);
+ if ($sRef eq 'CODE') {
+ my $x = $FILTER->(@$aFrame, $iFrame, $iLineFrame);
+ if (ref($x) eq 'ARRAY') {
+ $aFrame=$x;
+ } elsif (!$x) {
+ $aFrame=undef;
+ }
+ } elsif (($sRef eq 'ARRAY') && ! _isIgnored($sSub, $FILTER)) {
+ $aFrame=undef;
+ } elsif (($sRef eq 'Regexp') && !_isIgnored($sSub, [$FILTER])) {
+ $aFrame=undef;
+ }
+ push(@$st, $aFrame) if $aFrame;
+ }
+
+ $aCall = _cacheCall($iFrame++);
+ }
+
+ push @$st, [ $iFile, $iLine, "<package: $sPackage>", $$aCall ];
+ if ($e) { my $n=$#{$e->[2]}-$#$st;$e->[2]=[@{$e->[2]}[0..$n]]};
+ return $st;
+}
+
+#-----------------------------
+
+sub _isIgnored {
+ my ($sSub, $aIgnore) = @_;
+ foreach my $re (@$aIgnore) { return 1 if $sSub =~ $re; }
+ return 0;
+}
+
+#------------------------------------------------------------------
+
+sub _dumpMessage {
+ my ($e, $iDepth) = @_;
+
+ my $sMsg = $e->getMessage();
+ return $sMsg unless $STRINGIFY;
+ if (ref($STRINGIFY) eq 'CODE') {
+ return $STRINGIFY->($sMsg);
+ }
+
+ $iDepth = 0 unless defined($iDepth);
+ my $sIndent = ' ' x ($TAB*$iDepth);
+ $sMsg = "\n${sIndent}Exception! $sMsg";
+ return $sMsg if $STRINGIFY == 0;
+
+ my ($sThrow, $sReach);
+ my $sTab = ' ' x $TAB;
+
+ $sIndent.= $sTab;
+ if ($STRINGIFY > 2) {
+ my $aPropagation = $e->getPropagation();
+ for (my $i=$#$aPropagation; $i >= 0; $i--) {
+ my ($f,$l) = @{$aPropagation->[$i]};
+ $sMsg .= "\n${sIndent}rethrown at file $f, line $l";
+ }
+ $sMsg .= "\n";
+ $sThrow='thrown ';
+ $sReach='reached ';
+ } else {
+ $sThrow='';
+ $sReach='';
+ }
+
+ my $st=$e->getStackTrace();
+ my $iTop = scalar @$st;
+
+ for (my $iFrame=0; $iFrame<$iTop; $iFrame++) {
+ my ($f,$l,$s,$aArgs) = @{$st->[$iFrame]};
+
+ if ($iFrame) {
+ #2nd and following stack frame
+ my $sVia="${sIndent}${sReach}via file $f, line $l";
+ my $sLine="$sVia in $s";
+ $sMsg .= (length($sLine)>$LINE_LENGTH
+ ? "\n$sVia\n$sIndent${sTab}in $s" : "\n$sLine");
+ } else {
+ # first stack frame
+ my $tid=$e->getTid();
+ my $sAt="${sIndent}${sThrow}at file $f, line $l";
+ my $sLine="$sAt in $s";
+ $sMsg .= (length($sLine)>$LINE_LENGTH
+ ? "\n$sAt\n$sIndent${sTab}in $s" : "\n$sLine")
+ . ", pid=" . $e->getPid() . (defined($tid)?", tid=$tid":'');
+
+ return "$sMsg\n" if $STRINGIFY == 1;
+ }
+
+ if ($STRINGIFY > 3) {
+ my $bTop = ($iFrame+1) == $iTop;
+ my $sVar= ($bTop && !$iDepth) ? '@ARGV' : '@_';
+ my $bMaybeEatenByGetOpt = $bTop && !scalar(@$aArgs)
+ && exists($INC{'Getopt/Long.pm'});
+
+ my $sVarIndent = "\n${sIndent}" . (' ' x $TAB);
+ my $sArgPrefix = "${sVarIndent}".(' ' x length($sVar)).' ';
+ if ($bMaybeEatenByGetOpt) {
+ $sMsg .= $sArgPrefix . $sVar
+ . '() # maybe eaten by Getopt::Long?';
+ } else {
+ my $sArgs = join($sArgPrefix.',', @$aArgs);
+ $sMsg .= "${sVarIndent}$sVar=($sArgs";
+ $sMsg .= $sArgs ? "$sArgPrefix)" : ')';
+ }
+ }
+ }
+ $sMsg.="\n";
+ return $sMsg if $STRINGIFY == 2;
+
+ my $eChained = $e->getChained();
+ if (defined($eChained)) {
+ my $sTrigger = isException($eChained)
+ ? _dumpMessage($eChained, $iDepth+1)
+ : "\n${sIndent}$eChained\n";
+ $sMsg .= "\n${sIndent}Triggered by...$sTrigger";
+ }
+ return $sMsg;
+}
+
+#------------------------------------------------------------------
+
+# refaddr has a prototype($) so we can't use it directly as an
+# overload operator: it complains about being passed 3 parameters
+# instead of 1.
+sub _refaddr { Scalar::Util::refaddr($_[0]) };
+
+#------------------------------------------------------------------
+
+sub _rethrow {
+ my $self = shift; my $sAddOrOmit = shift;
+ my ($p,$f,$l)=caller(1);
+ $self->PROPAGATE($f,$l);
+
+ if (@_%2) {
+ warn sprintf('bad parameter list to %s->rethrow(...)'
+ .'at file %d, line %d: odd number of elements in property-value '
+ .'list, property value has no property name and will be '
+ ."discarded (common causes: you have %s string)\n"
+ ,$f, $l, $sAddOrOmit);
+ shift @_;
+ }
+ $self->replaceProperties({@_}) if (@_);
+ return $self;
+}
+
+#------------------------------------------------------------------
+# Traps warnings and reworks them so that they tell the user how
+# to fix the problem rather than obscurely complain about an
+# invisible sprintf with uninitialized values that seem to come from
+# no where (and make Exception::Lite look like it is broken)
+
+sub _sprintf {
+ my $sMsg;
+ my $sWarn;
+
+ {
+ local $SIG{__WARN__} = sub { $sWarn=$_[0] if !defined($sWarn) };
+
+ # sprintf has prototype ($@)
+ my $sFormat = shift;
+ $sMsg = sprintf($sFormat, @_);
+ }
+
+ if (defined($sWarn)) {
+ my $sReason='';
+ my ($f, $l, $s) = (caller(1))[1,2,3];
+ $s =~ s/::(\w+)\z/->$1/;
+ $sWarn =~ s/sprintf/$s/;
+ $sWarn =~ s/\s+at\s+[\w\/\.]+\s+line\s+\d+\.\s+\z//;
+ if ($sWarn
+ =~ m{^Use of uninitialized value in|^Missing argument}) {
+ my $p=$s; $p =~ s/->\w+\z//;
+ $sReason ="\n Most likely cause: "
+ . "Either you are missing property-value pairs needed to"
+ . "build the message or your exception class's format"
+ . "definition mistakenly has too many placeholders "
+ . "(e.g. %s,%d,etc)\n";
+ }
+ warn "$sWarn called at file $f, line $l$sReason\n";
+ }
+ return $sMsg;
+}
+
+#------------------------------------------------------------------
+
+sub _shiftProperties {
+ my $cl= shift; my $st=shift; my $sAddOrOmit = shift;
+ if (@_%2) {
+ $"='|';
+ warn sprintf('bad parameter list to %s->new(...) at '
+ .'file %s, line %d: odd number of elements in property-value '
+ .'list, property value has no property name and will be '
+ .'discarded (common causes: you have %s string -or- you are '
+ ."using a string as a chained exception)\n"
+ ,$cl,$st->[0]->[0],$st->[0]->[1], $sAddOrOmit);
+ shift @_;
+ }
+ return {@_};
+}
+
+#==================================================================
+# MODULE INITIALIZATION
+#==================================================================
+
+declareExceptionClass(__PACKAGE__ .'::Any');
+1;
--- /dev/null
+=head1 NAME
+
+Exception::Lite - light weight exception handling class with smart
+stack tracing, chaining, and localization support.
+
+=head1 SYNOPSIS
+
+ # --------------------------------------------------------
+ # making this module available to your code
+ # --------------------------------------------------------
+
+ #Note: there are NO automatic exports
+
+ use Exception::Lite qw(declareExceptionClass
+ isException
+ isChainable
+ onDie
+ onWarn);
+
+ # imports only: declareExceptionClass isException isChainable
+ use Exception::Lite qw(:common);
+
+ # imports all exportable methods listed above
+ use Exception::Lite qw(:all);
+
+
+ # --------------------------------------------------------
+ # declare an exception class
+ # --------------------------------------------------------
+
+ # no format rule
+ declareExceptionClass($sClass);
+ declareExceptionClass($sClass, $sSuperClass);
+
+ # with format rule
+ declareExceptionClass($sClass, $aFormatRule);
+ declareExceptionClass($sClass, $sSuperClass, $aFormatRule);
+
+ # with customized subclass
+ declareExceptionClass($sClass, $sSuperClass, 1);
+ declareExceptionClass($sClass, $aFormatRule, 1);
+ declareExceptionClass($sClass, $sSuperClass, $aFormatRule, 1);
+
+ # --------------------------------------------------------
+ # throw an exception
+ # --------------------------------------------------------
+
+ die $sClass->new($sMsg, $prop1 => $val1, ...); #no format rule
+ die $sClass->new($prop1 => $val1, ...); #has format rule
+
+ #-or-
+
+ $e = $sClass->new($sMsg, $prop1 => $val1, ...); #no format rule
+ $e = $sClass->new($prop1 => $val1, ...); #has format rule
+
+ die $e;
+
+ # --------------------------------------------------------
+ # catch and test an exception
+ # --------------------------------------------------------
+
+ # Note: for an explanation of why we don't use if ($@)... here,
+ # see Catching and Rethrowing exceptions below
+
+ eval {
+ .... some code that may die here ...
+ return 1;
+ } or do {
+ my $e=$@;
+
+ if (isException($e, 'Class1')) {
+ ... do something ...
+ } elsif (isExcption($e, 'Class2')) {
+ ... do something else ...
+ }
+ };
+
+ isException($e); # does $e have the above exception methods?
+ isException($e,$sClass) # does $e belong to $sClass or a subclass?
+
+ # --------------------------------------------------------
+ # getting information about an exception object
+ # --------------------------------------------------------
+
+ $e->getMessage();
+ $e->getProperty($sName);
+ $e->isProperty($sName);
+ $e->replaceProperties($hOverride);
+
+ $e->getPid();
+ $e->getPackage();
+ $e->getTid();
+
+ $e->getStackTrace();
+ $e->getFrameCount();
+ $e->getFile($i);
+ $e->getLine($i);
+ $e->getSubroutine($i);
+ $e->getArgs($i);
+
+ $e->getPropagation();
+ $e->getChained();
+
+
+ # --------------------------------------------------------
+ # rethrowing exceptions
+ # --------------------------------------------------------
+
+ # using original properties and message
+
+ $@=$e; die; # pure Perl way (reset $@ in case wiped out)
+
+ die $e->rethrow(); # same thing, but a little less cryptic
+
+
+ # overriding original message/properties
+
+ die $e->rethrow(path=>$altpath, user=>$nameReplacingId);
+
+
+ # --------------------------------------------------------
+ # creation of chained exceptions (one triggered by another)
+ # (new exception with "memory" of what caused it and stack
+ # trace from point of cause to point of capture)
+ # --------------------------------------------------------
+
+ isChainable($e); # can $e be used as a chained exception?
+
+ die $sClass->new($e, $sMsg, $prop1 => $val1, ...);#no format rule
+ die $sClass->new($e, $prop1 => $val1, ...); #has format rule
+
+ # --------------------------------------------------------
+ # print out full message from an exception
+ # --------------------------------------------------------
+
+ print $e # print works
+ warn $e # warn works
+ print "$e\n"; # double quotes work
+ my $sMsg=$e."\n"; print $sMsg; # . operator works
+
+
+ # --------------------------------------------------------
+ # global control variables (maybe set on the command line)
+ # --------------------------------------------------------
+
+ $Exception::Lite::STRINGIFY #set rule for stringifying messages
+
+ = 1; # message and file/line where it occured
+ = 2; # 1 + what called what (simplified stack trace)
+ = 3; # 2 + plus any chained exceptions and where message
+ # was caught, if propagated and rethrown
+ = 4; # 3 + arguments given to each call in stack trace
+ = coderef # custom formatting routine
+
+ $Exception::Lite::TAB # set indentation for stringified
+ # messages, particularly indentation for
+ # call parameters and chained exceptions
+
+ $Exception::Lite::FILTER
+ = 0 # see stack exactly as Perl does
+ = 1 # remove frames added by eval blocks
+ = coderef # custom filter - see getStackTrace for details
+
+ # --------------------------------------------------------
+ # controlling the stack trace from the command line
+ # --------------------------------------------------------
+
+ perl -mException::Lite=STRINGIFY=1,FILTER=0,TAB=4
+ perl -m'Exception::Lite qw(STRINGIFY=1 FILTER=0 TAB=4)'
+
+ # --------------------------------------------------------
+ # built in exception classes
+ # --------------------------------------------------------
+
+ # generic wrapper for converting exception strings and other
+ # non-Exception::Lite exceptions into exception objects
+
+ Exception::Class::Any->new($sMessageText);
+
+To assist in debugging and testing, this package also includes
+two methods that set handlers for die and warn. These methods
+should I<only> be used temporarily during active debugging. They
+should not be used in production software, least they interfere
+with the way other programmers using your module wish to do their
+debugging and testing.
+
+ # --------------------------------------------------------
+ # force all exceptions/warnings to use Exception::Lite to
+ # print out messages and stack traces
+ # --------------------------------------------------------
+
+ # $stringify is the value for EXCEPTION::Lite::STRINGIFY
+ # that you want to use locally to print out messages. It
+ # will have no effect outside of the die handler
+
+ Exception::Lite::onDie($stringify);
+ Exception::Lite::onWarn($stringify);
+
+=head1 DESCRIPTION
+
+The C<Exception::Lite> class provides an easy and very light weight
+way to generate context aware exceptions. It was developed because
+the exception modules on CPAN as of December,2010 were heavy on
+features I didn't care for and did not have the features I most
+needed to test and debug code efficiently.
+
+=head2 Features
+
+This module provides a light weight but powerful exception class
+that
+
+=over
+
+=item *
+
+provides an uncluttered stack trace that clearly shows what
+called what and what exception triggered what other exception.
+It significantly improves on the readability of the stack trace
+dumps provided by C<carp> and other exception modules on
+CPAN (as of 12/2010). For further discussion and a sample, see
+L</More intelligent stack trace>.
+
+=item *
+
+gives the user full control over the amount of debugging
+information displayed when exceptions are thrown.
+
+=item *
+
+permits global changes to the amount of debugging information
+displayed via the command line.
+
+=item *
+
+closely integrates exception classes, messages, and properties
+so that they never get out of sync with one another. This in
+turn eliminates redundant coding and helps reduce the cost of
+writing,validating and maintaining a set of exceptions.
+
+=item *
+
+is easy to retrofit with native language support, even if this
+need appears late in the development process.This makes it
+suitable for use with agile development strategies.
+
+=item *
+
+act like strings in string context but are in fact objects with
+a class hierarchy and properties.They can be thrown and rethrown
+with standard Perl syntax. Like any object, they can be uniquely
+identified in numeric context where they equal their reference
+address (the value returned by C<Scalar::Util::refaddr()>.
+
+=item *
+
+does not interfere with signal handlers or the normal Perl syntax
+and the assumptions of Perl operators.
+
+=item *
+
+can be easily extended and subclassed
+
+=back
+
+=head2 Lightweight how?
+
+Despite these features C<Exception::Lite> maintains its "lite"
+status by
+
+=over
+
+=item *
+
+using only core modules
+
+=item *
+
+generating tiny exception classes (30-45LOC per class).
+
+=item *
+
+eliminating excess baggage by customizing generated classes to
+ reflect the actual needs of exception message generation. For
+ instance an exception wrapped around a fixed string message would
+ omit code for message/property integration and would be little
+ more than a string tied to a stack trace and property hash.
+
+=item *
+
+storing only the minimum amount of stack trace data needed to
+ generate exception messages and avoiding holding onto references
+ from dead stack frames. (Note: some CPAN modules hold onto
+ actual variables from each frame, possibly interfering with
+ garbage collection).
+
+=item *
+
+doing all its work, including class generation and utilities in
+ a single file that is less than half the size of the next smallest
+ similarly featured all-core exception class on CPAN (support for
+ both properties and a class heirarchy). C<Exception::Lite>
+ contains about 400 lines when developer comments are excluded). The
+ next smallest all core module is L<Exception::Base|Exception::Base>
+ which clocks in at just over 1000 lines after pod and developer
+ comments are excluded).
+
+=item *
+
+avoiding a heavy-weight base class. Code shared by
+ C<Exception::Lite> classes are stored in function calls that total
+ 230 or so lines of code relying on nothing but core modules. This
+ is significantly less code than is needed by the two CPAN packages
+ with comparable features. The all core
+ L<Exception::Base|Exception::Base> class contains 700+ lines of
+ code. The base class of L<Exception::Class|Exception::Class> has
+ 200 lines of its own but drags in two rather large non-core
+ modules as dependencies: L<Devel::StackTrace|Devel::StackTrace>
+ L<Class::Data::Inheritable|Class::Data::Inheritable>.
+
+=back
+
+C<Exception::Lite> has more features (chaining, message/property
+integration) but less code due to the following factors:
+
+=over
+
+=item *
+
+working with Perl syntax rather than trying to replace it.
+
+=item *
+
+using a light approach to OOP - exception classes have just enough
+and no more OO features than are needed to be categorized by a
+class, participate in a class heirarchy and to have properties.
+
+=item *
+
+respecting separation of concerns. C<Exception::Lite> focuses
+on the core responsibility of an exception and leaves the bulk of
+syntax creation (e.g. Try/Catch) to specialist modules like
+L<Try::Tiny|Try::Tiny>. Other modules try to double as
+comprehensive providers of exception related syntactic sugar.
+
+=item *
+
+not trying to be the only kind of exception that an application
+uses.
+
+=back
+
+=head1 USAGE
+
+=head2 Defining Exception Classes
+
+C<Exception::Lite> provides two different ways to define messages.
+The first way, without a format rule, lets you compose a freeform
+message for each exception. The second way, with a format rule,
+lets you closely integrate messages and properties and facilitates
+localization of messages for any packages using your software.
+
+=head3 Defining freeform messages
+
+If you want to compose a free form message for each and every
+exception, the class declaration is very simple:
+
+ declareExceptionClass($sClass);
+ declareExceptionClass($sClass, $sSuperClass);
+
+ # with customized subclass
+ declareExceptionClass($sClass, $sSuperClass, 1);
+
+C<$sClass> is the name of the exception class.
+
+C<$sSuperClass> is the name of the superclass, if there is one.
+The superclass can be any class created by C<Exception::Lite>. It
+can also be any role class, i.e. a class that has methods but no
+object data of its own.
+
+The downside of this simple exception class is that there is
+absolutely no integration of your messages and any properties that
+you assign to the exception. If you would like to see your property
+values included in the message string,consider using a formatted
+message instead.
+
+=head3 Defining formatted messages
+
+If you wish to include property values in your messages, you need
+to declare a formatted message class. To do this, you define a
+format rule and pass it to the constructor:
+
+ $aFormatRule = ['Cannot copy %s to %s', qw(from to) ];
+
+ declareExceptionClass($sClass, $aFormatRule);
+ declareExceptionClass($sClass, $sSuperClass, $aFormatRule);
+
+ # with customized subclass
+ declareExceptionClass($sClass, $aFormatRule, 1);
+ declareExceptionClass($sClass, $sSuperClass, $aFormatRule, 1);
+
+Format rules are nothing more than a sprintf message string
+followed by a list of properties in the same order as the
+placeholders in the message string. Later on when an exception
+is generated, the values of the properties will replace the
+property names. Some more examples of format rules:
+
+
+ $aFormatRule = ['Illegal argument <%s>: %s', qw(arg reason)];
+ declareExceptionClass('BadArg', $aFormatRule);
+
+ $aFormatRule = ['Cannot open file <%s>> %s', qw(file reason)];
+ declareExceptionClass('OpenFailed', $aFormatRule);
+
+ $sFormatRule = ['Too few %s, must be at least %s', qw(item min)];
+ declareExceptionClass('TooFewWidgets', $aFormatRule);
+
+
+Later on when you throw an exception you can forget about the message
+and set the properties, the class will do the rest of the work:
+
+ die BadArg->new(arg=>$sPassword, reason=>'Too few characters');
+
+
+ open(my $fh, '>', $sFile)
+ or die OpenFailed->new(file=>$sFile, reason=>$!);
+
+And still later when you catch the exception, you have two kinds
+of information for the price of one:
+
+ # if you catch BadArg
+
+ $e->getProperty('arg') # mine
+ $e->getProperty('reason') # too few characters
+ $e->getMessage() # Illegal argument <mine>: too few characters
+
+
+ # if you catch OpenFailed
+
+ $e->getProperty('file') # foo.txt
+ $e->getProperty('reason') # path not found
+ $e->getMessage() # Cannot open <foo.txt>: path not found
+
+
+=head2 Creating and throwing exceptions
+
+When it comes times to create an exception, you create and
+throw it like this (C<$sClass> is a placeholder for the name of
+your exception class);
+
+
+ die $sClass->new($sMsg, prop1 => $val1, ...); #no format rule
+ die $sClass->new(prop1 => $val1, ...); #has format rule
+
+ #-or-
+
+ $e = $sClass->new($sMsg, prop1 => $val1, ...); #no format rule
+ $e = $sClass->new(prop1 => $val1, ...); #has format rule
+
+ die $e;
+
+
+For example:
+
+ # Freeform exceptions (caller composes message, has message
+ # parameter ($sMsg) before the list of properties)
+
+ close $fh or die UnexpectedException
+ ->new("Couldn't close file handle (huh?): $!");
+
+ die PropertySettingError("Couldn't set property"
+ , prop=>foo, value=>bar);
+
+ # Formatted exceptions (no $sMsg parameter)
+
+ if (length($sPassword) < 8) {
+ die BadArg->new(arg=>$sPassword, reason=>'Too few characters');
+ }
+
+ open(my $fh, '>', $sFile)
+ or die OpenFailed->new(file=>$sFile, reason=>$!);
+
+In the above examples the order of the properties does not matter.
+C<Exception::Lite> is using the property names, not the order of
+the properties to find the right value to plug into the message
+format string.
+
+=head2 Catching and testing exceptions
+
+In Perl there are two basic ways to work with exceptions:
+
+* native Perl syntax
+
+* Java like syntax (requires non-core modules)
+
+=head3 Catching exceptions the Java way
+
+Java uses the following idiom to catch exceptions:
+
+ try {
+ .... some code here ...
+ } catch (SomeExceptionClass e) {
+ ... error handling code here ...
+ } catch (SomeOtherExceptionClass e) {
+ ... error handling code here ...
+ } finally {
+ ... cleanup code here ...
+ }
+
+There are several CPAN modules that provide some sort of syntactic
+sugar so that you can emulate java syntax. The one recommended
+for C<Exception::Lite> users is L<Try::Tiny|Try::Tiny>.
+L<Try::Tiny|Try::Tiny> is an elegant class that concerns itself
+only with making it possible to use java-like syntax. It can be
+used with any sort of exception.
+
+Some of the other CPAN modules that provide java syntax also
+require that you use their exception classes because the java like
+syntax is part of the class definition rather than a pure
+manipulation of Perl syntax.
+
+
+=head3 Catching exceptions the Perl way
+
+The most reliable and fastest way to catch an exception is to use
+C< eval/do >:
+
+ eval {
+ ...
+ return 1;
+ } or do {
+ # save $@ before using it - it can easily be clobbered
+ my $e=$@;
+
+ ... do something with the exception ...
+
+ warn $e; #use $e as a string
+ warn $e->getMessage(); # use $e as an object
+ }
+
+
+The C<eval> block ends with C<return 1;> to insure that successful
+completion of the eval block never results in an undefined value.
+In certain cases C<undef> is a valid return value for a statement,
+We don't want to enter the C<do> block for any reason other than
+a thrown exception.
+
+C< eval/do > is both faster and more reliable than the C< eval/if>
+which is commonly promoted in Perl programming tutorials:
+
+ # eval ... if
+
+ eval {...};
+ if ($@) {....}
+
+It is faster because the C<do> block is executed if and only
+if the eval fails. By contrast the C<if> must be evaluated both
+in cases of succes and failure.
+
+C< eval/do > is more reliable because the C<do> block is guaranteed
+to be triggered by any die, even one that accidentally throws undef
+or '' as the "exception". If an exception is thrown within the C<eval>
+block, it will always evaluate to C<undef> therefore triggering the
+C<do> block.
+
+On the other hand we can't guarentee that C<$@> will be defined
+even if an exception is thrown. If C<$@> is C<0>, C<undef>, or an
+empty string, the C<if> block will never be entered. This happens
+more often then many programmers realize. When eval exits the
+C< eval > block, it calls destructors of any C<my> variables. If
+any of those has an C< eval > statement, then the value of C<$@> is
+wiped clean or reset to the exception generated by the destructor.
+
+Within the C<do> block, it is a good idea to save C<$@> immediately
+into a variable before doing any additional work. Any subroutine
+you call might also clobber it. Even built-in commands that don't
+normally set C<$@> can because Perl lets a programmer override
+built-ins with user defined routines and those user define routines
+might set C<$@> even if the built-in does not.
+
+=head3 Testing exceptions
+
+Often when we catch an exception we want to ignore some, rethrow
+others, and in still other cases, fix the problem. Thus we need a
+way to tell what kind of exception we've caught. C<Exception::Lite>
+provides the C<isException> method for this purpose. It can be
+passed any exception, including scalar exceptions:
+
+ # true if this exception was generated by Exception::Line
+ isException($e);
+
+
+ # true if this exception belongs to $sClass. It may be a member
+ # of the class or a subclass. C<$sClass> may be any class, not
+ # just an Exception::Lite generated class. You can even use this
+ # method to test for string (scalar) exceptions:
+
+ isException($e,$sClass);
+
+ isException($e,'Excption::Class');
+ isException($e, 'BadArg');
+ isException($e, '');
+
+And here is an example in action. It converts an exception to a
+warning and determines how to do it by checing the class.
+
+
+ eval {
+ ...
+ return 1;
+ } or do {
+ my $e=$@;
+ if (Exception::Lite::isException($e)) {
+
+ # get message w/o stack trace, "$e" would produce trace
+ warn $e->getMessage();
+
+ } elsif (Exception::Lite::isException('Exception::Class') {
+
+ # get message w/o stack trace, "$e" would produce trace
+ warn $e->message();
+
+ } elsif (Exception::Lite::isException($e,'')) {
+
+ warn $e;
+ }
+ }
+
+=head2 Rethrowing exceptions
+
+Perl doesn't have a C<rethrow> statement. To reliably rethrow an
+exception, you must set C<$@> to the original exception (in case it
+has been clobbered during the error handling process) and then call
+C<die> without any arguments.
+
+ eval {
+ ...
+ return 1;
+ } or do {
+ my $e=$@;
+
+ # do some stuff
+
+ # rethrow $e
+ $@=$e; die;
+ }
+
+The above code will cause the exception's C<PROPAGATE> method to
+record the file and line number where the exception is rethrown.
+See C<getLine>, C<getFile>, and C<getPropagation> in the class
+reference below for more information.
+
+As this Perl syntax is not exactly screaming "I'm a rethrow",
+C<Exception::Lite> provides an alternative and hopefully more
+intuitive way of propagating an exception. There is no magic here,
+it just does what perl would do had you used the normal syntax,
+i.e. call the exception's C<PROPAGATE> method.
+
+ eval {
+ ...
+ return 1;
+ } or do {
+ my $e=$@;
+
+ # rethrow $e
+ die $e->rethrow();
+ }
+
+=head2 Chaining Messages
+
+As an exception moves up the stack, its meaning may change. For
+example, suppose a subroutine throws the message "File not open".
+The immediate caller might be able to use that to try and open
+a different file. On the other hand, if the message gets thrown
+up the stack, the fact that a file failed to open might not
+have any meaning at all. That higher level code only cares that
+the data it needed wasn't available. When it notifies the user,
+it isn't going to say "File not found", but "Can't run market
+report: missing data feed.".
+
+When the meaning of the exception changes, it is normal to throw
+a new exception with a class and message that captures the new
+meaning. However, if this is all we do, we lose the original
+source of the problem.
+
+Enter chaining. Chaining is the process of making one exception
+"know" what other exception caused it. You can create a new
+exception without losing track of the original source of the
+problem.
+
+To chain exceptions is simple: just create a new exception and
+pass the caught exception as the first parameter to C<new>. So
+long as the exception is a non-scalar, it will be interpreted
+as a chained exception and not a property name or message text
+(the normal first parameter of C<new>).
+
+Chaining is efficient, especially if the chained exception is
+another C<Exception::Lite> exception. It does not replicate
+the stack trace. Rather the original stack trace is shorted to
+include only the those fromes frome the time it was created to
+the time it was chained.
+
+Any non-scalar exception can be chained. To test whether or not
+a caught exception is chainable, you can use the method
+C<isChainable>. This method is really nothing more than
+a check to see if the exception is a non-scalar, but it helps
+to make your code more self documenting if you use that method
+rather than C<if (ref($e))>.
+
+If an exception isn't chainable, and you still want to chain
+it, you can wrap the exception in an exception class. You
+can use the built-in C<Exception::Class::Any> or any class of
+your own choosing.
+
+ #-----------------------------------------------------
+ # define some classes
+ #-----------------------------------------------------
+
+ # no format rule
+ declareExceptionClass('HouseholdDisaster');
+
+ # format rule
+ declareExceptionClass('ProjectDelay'
+ , ['The project was delayed % days', qw(days)]);
+
+ #-----------------------------------------------------
+ # chain some exceptins
+ #-----------------------------------------------------
+
+ eval {
+ .... some code here ...
+ return 1;
+ } or do {
+ my $e=$@;
+ if (Exception::Lite::isChainable($e)) {
+ if (Exception::Lite::isException($e, 'FooErr') {
+ die 'SomeNoFormatException'->new($e, "Caught a foo");
+ } else {
+ die 'SomeFormattedException'->new($e, when => 'today');
+ }
+ } elsif ($e =~ /fire/) {
+ die 'Exception::Lite::Any'->new($e);
+ die 'SomeFormattedException'->new($e, when => 'today');
+ } else {
+ # rethrow it since we can't chain it
+ $@=$e; die;
+ }
+ }
+
+=head2 Reading Stack Traces
+
+At its fullest level of detail, a stack trace looks something
+like this:
+
+ Exception! Mayhem! and then ...
+
+ thrown at file Exception/Lite.t, line 307
+ in main::weKnowBetterThanYou, pid=24986, tid=1
+ @_=('ARRAY(0x83a8a90)'
+ ,'rot, rot, rot'
+ ,'Wikerson brothers'
+ ,'triculous tripe'
+ ,'There will be no more talking to hoos who are not!'
+ ,'black bottom birdie'
+ ,'from the three billionth flower'
+ ,'Mrs Tucanella returns with uncles and cousins'
+ ,'sound off! sound off! come make yourself known!'
+ ,'Apartment 12J'
+ ,'Jo Jo the young lad'
+ ,'the whole world was saved by the smallest of all'
+ )
+ reached via file Exception/Lite.t, line 281
+ in main::notAWhatButAWho
+ @_=()
+ reached via file Exception/Lite.t, line 334 in main::__ANON__
+ @_=()
+ reached via file Exception/Lite.t, line 335 in <package: main>
+ @ARGV=()
+
+ Triggered by...
+ Exception! Horton hears a hoo!
+ rethrown at file Exception/Lite.t, line 315
+
+ thrown at file Exception/Lite.t, line 316
+ in main::horton, pid=24986, tid=1
+ @_=('15th of May'
+ ,'Jungle of Nool'
+ ,'a small speck of dust on a small clover'
+ ,'a person's a person no matter how small'
+ )
+ reached via file Exception/Lite.t, line 310 in main::hoo
+ @_=('Dr Hoovey'
+ ,'hoo-hoo scope'
+ ,'Mrs Tucanella'
+ ,'Uncle Nate'
+ )
+ reached via file Exception/Lite.t, line 303
+ in main::weKnowBetterThanYou
+ @_=('ARRAY(0x83a8a90)'
+ ,'rot, rot, rot'
+ ,'Wikerson brothers'
+ ,'triculous tripe'
+ ,'There will be no more talking to hoos who are not!'
+ ,'black bottom birdie'
+ ,'from the three billionth flower'
+ ,'Mrs Tucanella returns with uncles and cousins'
+ ,'sound off! sound off! come make yourself known!'
+ ,'Apartment 12J'
+ ,'Jo Jo the young lad'
+ ,'the whole world was saved by the smallest of all'
+ )
+
+
+=over
+
+=item *
+
+lines begining with "thrown" indicate a line where a new exception
+was thrown. If an exception was chained, there might be multiple
+such lines.
+
+=item *
+
+lines beginning with "reached via" indicate the path travelled
+I<down> to the point where the exception was thrown. This is the
+code that was excuted before the exception was triggered.
+
+=item *
+
+lines beginning with "rethrown at" indicate the path travelled
+I<up> the stack by the exception I<after> it was geenerated. Each
+line indicates a place where the exception was caught and rethrown.
+
+=item *
+
+lines introduced with "Triggered by" are exceptions that were
+chained together. The original exception is the last of the
+triggered exceptions. The original line is the "thrown" line
+for the original exception.
+
+=item *
+
+C<@_> and <C@ARGV> below a line indicates what is left of the
+parameters passed to a method, function or entry point routine.
+In ideal circumstances they are the parameters passed to the
+subroutine mentioned in the line immediately above C<@_>. In
+reality, they can be overwritten or shifted away between the
+point when the subroutine started and the line was reached.
+
+Note: if you use L<Getopt::Long> to process C<@ARGV>, C<@ARGV>
+will be empty reduced to an empty array. If this bothers you, you
+can localize <@ARGV> before calling C<GetOptions>, like this:
+
+ my %hARGV;
+ {
+ local @ARGV = @ARGV;
+ GetOptions(\%hARGV,...);
+ }
+
+=item *
+
+pid is the process id where the code was running
+
+=item *
+
+tid is the thread id where the code was running
+
+=back
+
+=head1 SPECIAL TOPICS
+
+=head2 Localization of error messages
+
+Rather than treat the error message and properties as entirely
+separate entities, it gives you the option to define a format string
+that will take your property values and insert them automatically
+into your message. Thus when you generate an exception, you can
+specify only the properties and have your message automatically
+generated without any need to repeat the property values in messy
+C<sprintf>'s that clutter up your program.
+
+One can localize from the very beginning when one declares the
+class or later on after the fact if you are dealing with legacy
+software or developing on an agile module and only implementing
+what you need now.
+
+To localize from the get-go:
+
+ # myLookupSub returns the arguments to declareException
+ # e.g. ('CopyError', [ 'On ne peut pas copier de %s a %s'
+ , qw(from to)])
+
+ declareExceptionClass( myLookupSub('CopyError', $ENV{LANG}) );
+
+
+ # .... later on, exception generation code doesn't need to
+ # know or care about the language. it just sets the properties
+
+
+ # error message depends on locale:
+ # en_US: 'Cannot copy A.txt to B.txt'
+ # fr_FR: 'On ne peut pas copier de A.txt a B.txt'
+ # de_DE: 'Kann nicht kopieren von A.txt nach B.txt'
+
+ die 'CopyError'->new(from => 'A.txt', to => 'B.txt');
+
+
+Another alternative if you wish to localize from the get-go is
+to pass a code reference instead of a format rule array. In this
+case, C<Exception::Lite> will automatically pass the class name
+to the subroutine and retrieve the value returned.
+
+
+ # anothherLookupSub has parameters ($sClass) and returns
+ # a format array, for example:
+ #
+ # %LOCALE_FORMAT_HASH = (
+ # CopyError => {
+ # en_US => ['Cannot copy %s to %s', qw(from to)]
+ # ,fr_FR => ['On ne peut pas copier de %s a %s', qw(from to)]
+ # ,de_DE => ['Kann nicht kopieren von %s nach %s''
+ # , qw(from to)]
+ #
+ # AddError => ...
+ # );
+ #
+ # sub anotherLookupSub {
+ # my ($sClass) = @_;
+ # my $sLocale = $ENV{LANG}
+ # return $LOCALE_FORMAT_HASH{$sClass}{$sLocale};
+ # }
+ #
+
+ declareExceptionClass('CopyError', &anotherLookupSub);
+ declareExceptionClass('AddError', &anotherLookupSub);
+
+
+ # error message depends on locale:
+ # en_US: 'Cannot copy A.txt to B.txt'
+ # fr_FR: 'On ne peut pas copier de A.txt a B.txt'
+ # de_DE: 'Kann nicht kopieren von A.txt nach B.txt'
+
+ die CopyError->new(from => 'A.txt', to => 'B.txt');
+ die AddError->new(path => 'C.txt');
+
+
+If you need to put in localization after the fact, perhaps for a
+new user interface you are developing, the design pattern might
+look like this:
+
+ # in the code module you are retrofitting would be an exception
+ # that lived in a single language world.
+
+ declareExceptionClass('CopyError'
+ ['Cannot copy %s to %s', [qw(from to)]);
+
+
+ # in your user interface application.
+
+ if (isException($e, 'CopyError') && isLocale('fr_FR')) {
+ my $sFrom = $e->getProperty('from');
+ my $sTo = $e->getProperty('to');
+ warn sprintf('On ne peut pas copier de %s a %s', $sFrom,$sTo);
+ }
+
+=head2 Controlling verbosity and stack tracing
+
+You don't need to print out the fully verbose stack trace and in
+fact, by default you won't. The default setting, prints out
+only what called what. To make it easier to see what called what,
+it leaves out all of the dumps of C<@_> and C<@ARGV>.
+
+If you want more or less verbosity or even an entirely different
+trace, C<Exception::Lite> is at your sevice. It provides a variety
+of options for controlling the output of the exception:
+
+* Adjusting the level of debugging information when an exception is
+ thrown by setting C<$Exception::Lite::STRINGIFY>
+ in the program or C<-mException::Lite=STRINGIFY=level> on the
+ command line. This can be set to either a verbosity level or to
+ an exception stringification routine of your own choosing.
+
+* Control which stack frames are displayed by setting
+ C<$Exception::Lite::FILTER>. By default, only calls within named
+ and anonymous subroutines are displayed in the stack trace. Perl
+ sometimes creates frames for blocks of code within a subroutine.
+ These are omitted by default. If you want to see them, you can
+ turn filterin off. Alternatively you can set up an entirely
+ custon stack filtering rule by assigning a code reference to
+ C<$Exception::Lite::FILTER>.
+
+* By default, exceptions store and print a subset of the data
+ available for each stack frame. If you would like to display
+ richer per-frame information, you can do that too. See below
+ for details.
+
+=head3 Verbosity level
+
+The built-in rules for displaying exceptions as strings offer five
+levels of detail.
+
+* 0: Just the error message
+
+* 1: the error message and the file/line number where it occured
+ along with pid and tid.
+
+* 2: the error message and the calling sequence from the point where
+ the exception was generated to the package or script entry point
+ The calling sequence shows only file, line number and the name
+ of the subroutine where the exception was generated. It is not
+ cluttered with parameters, making it easy to scan.
+
+* 3: similar to 2, except that propagation and chained exceptions
+ are also displayed.
+
+* 4: same as 3, except that the state of C<@_> or C<@ARGV> at the
+ time the exception was thrown is also displayed. usually this
+ is the parameters that were passed in, but it may include several
+ leading C<undef> if C<shift> was used to process the parameter
+ list.
+
+Here are some samples illustrating different level of debugging
+information and what happens when the filter is turned off
+
+ #---------------------------------------------------
+ #Sample exception STRINGIFY=0 running on thread 5
+ #---------------------------------------------------
+
+ Exception! Mayhem! and then ...
+
+ #---------------------------------------------------
+ #Sample exception STRINGIFY=1 running on thread 5
+ #---------------------------------------------------
+
+ Exception! Mayhem! and then ...
+ at file Exception/Lite.t, line 307 in main::weKnowBetterThanYou, pid=24986, tid=5
+
+ #---------------------------------------------------
+ #Sample exception STRINGIFY=2 running on thread 4
+ #---------------------------------------------------
+
+ Exception! Mayhem! and then ...
+ at file Exception/Lite.t, line 307 in main::weKnowBetterThanYou, pid=24986, tid=4
+ via file Exception/Lite.t, line 281 in main::notAWhatButAWho
+ via file Exception/Lite.t, line 373 in main::__ANON__
+ via file Exception/Lite.t, line 374 in <package: main>
+
+ #---------------------------------------------------
+ #Sample exception STRINGIFY=3 running on thread 3
+ #---------------------------------------------------
+
+ Exception! Mayhem! and then ...
+
+ thrown at file Exception/Lite.t, line 307 in main::weKnowBetterThanYou, pid=24986, tid=3
+ reached via file Exception/Lite.t, line 281 in main::notAWhatButAWho
+ reached via file Exception/Lite.t, line 362 in main::__ANON__
+ reached via file Exception/Lite.t, line 363 in <package: main>
+
+ Triggered by...
+ Exception! Horton hears a hoo!
+ rethrown at file Exception/Lite.t, line 315
+
+ thrown at file Exception/Lite.t, line 316 in main::horton, pid=24986, tid=3
+ reached via file Exception/Lite.t, line 310 in main::hoo
+ reached via file Exception/Lite.t, line 303 in main::weKnowBetterThanYou
+
+ #---------------------------------------------------
+ #Sample exception STRINGIFY=3 running on thread 2
+ #FILTER=OFF (see hidden eval frames)
+ #---------------------------------------------------
+
+ Exception! Mayhem! and then ...
+
+ thrown at file Exception/Lite.t, line 307 in main::weKnowBetterThanYou, pid=24986, tid=2
+ reached via file Exception/Lite.t, line 281 in main::notAWhatButAWho
+ reached via file Exception/Lite.t, line 348 in (eval)
+ reached via file Exception/Lite.t, line 348 in main::__ANON__
+ reached via file Exception/Lite.t, line 350 in (eval)
+ reached via file Exception/Lite.t, line 350 in <package: main>
+
+ Triggered by...
+ Exception! Horton hears a hoo!
+ rethrown at file Exception/Lite.t, line 315
+
+ thrown at file Exception/Lite.t, line 316 in main::horton, pid=24986, tid=2
+ reached via file Exception/Lite.t, line 310 in (eval)
+ reached via file Exception/Lite.t, line 315 in main::hoo
+ reached via file Exception/Lite.t, line 303 in (eval)
+ reached via file Exception/Lite.t, line 305 in main::weKnowBetterThanYou
+
+ #---------------------------------------------------
+ #Sample exception STRINGIFY=4 running on thread 1
+ #FILTER=ON
+ #---------------------------------------------------
+
+ Exception! Mayhem! and then ...
+
+ thrown at file Exception/Lite.t, line 307 in main::weKnowBetterThanYou, pid=24986, tid=1
+ @_=('ARRAY(0x83a8a90)'
+ ,'rot, rot, rot'
+ ,'Wikerson brothers'
+ ,'triculous tripe'
+ ,'There will be no more talking to hoos who are not!'
+ ,'black bottom birdie'
+ ,'from the three billionth flower'
+ ,'Mrs Tucanella returns with Wikerson uncles and cousins'
+ ,'sound off! sound off! come make yourself known!'
+ ,'Apartment 12J'
+ ,'Jo Jo the young lad'
+ ,'the whole world was saved by the tiny Yopp! of the smallest of all'
+ )
+ reached via file Exception/Lite.t, line 281 in main::notAWhatButAWho
+ @_=()
+ reached via file Exception/Lite.t, line 334 in main::__ANON__
+ @_=()
+ reached via file Exception/Lite.t, line 335 in <package: main>
+ @ARGV=()
+
+ Triggered by...
+ Exception! Horton hears a hoo!
+ rethrown at file Exception/Lite.t, line 315
+
+ thrown at file Exception/Lite.t, line 316 in main::horton, pid=24986, tid=1
+ @_=('15th of May'
+ ,'Jungle of Nool'
+ ,'a small speck of dust on a small clover'
+ ,'a person's a person no matter how small'
+ )
+ reached via file Exception/Lite.t, line 310 in main::hoo
+ @_=('Dr Hoovey'
+ ,'hoo-hoo scope'
+ ,'Mrs Tucanella'
+ ,'Uncle Nate'
+ )
+ reached via file Exception/Lite.t, line 303 in main::weKnowBetterThanYou
+ @_=('ARRAY(0x83a8a90)'
+ ,'rot, rot, rot'
+ ,'Wikerson brothers'
+ ,'triculous tripe'
+ ,'There will be no more talking to hoos who are not!'
+ ,'black bottom birdie'
+ ,'from the three billionth flower'
+ ,'Mrs Tucanella returns with Wikerson uncles and cousins'
+ ,'sound off! sound off! come make yourself known!'
+ ,'Apartment 12J'
+ ,'Jo Jo the young lad'
+ ,'the whole world was saved by the tiny Yopp! of the smallest of all'
+ )
+
+
+=head3 Custom stringification subroutines
+
+The custom stringification subroutine expects one parameter, the
+exception to be stringified. It returns the stringified form of
+the exception. Here is an example of a fairly silly custom
+stringification routine that just prints out the chained messages
+without any stack trace:
+
+ $Exception::Lite::STRINGIFY = sub {
+ my $e=$_[0]; # exception is sole input parameter
+ my $sMsg='';
+ while ($e) {
+ $sMsg .= $e->getMessage() . "\n";
+ $e= $e->getChained();
+ }
+ return $sMsg; # return string repreentation of message
+ };
+
+=head3 Adding information to the stack trace
+
+By default, each frame of the stack trace contains only the file,
+line, containing subroutine, and the state of C<@_> at the time
+C<$sFile>,C<$iLine> was reached.
+
+If your custom subroutine needs more information about the stack
+than C<Exception::Lite> normally provides, you can change the
+contents of the stack trace by assigning a custom filter routine
+to C<$Exception::Lite::FILTER>.
+
+The arguments to this subroutine are:
+
+
+ ($iFrame, $sFile, $iLine $sSub, $aArgs, $iSubFrame, $iLineFrame)
+
+where
+
+* C<$sFile> is the file of the current line in that frame
+
+* C<$iLine> is the line number of current line in that frame
+
+* C<$sSub> is the name of the subroutine that contains C<$sFile> and
+ C<$iLine>
+
+* C<$aArgs> is an array that holds the stringified value of each
+ member of @_ at the time the line at C<$sFile>, C<$sLine> was
+ called. Usually, this is the parameters passed into C<$sSub>,
+ but may not be.
+
+* C<$iSubFrame> is the stack frame that provided the name of the sub
+ and the contents of $aArgs.
+
+* C<$iLineFrame> is the stack frame that provided the file and line
+ number for the frame.
+
+Please be aware that each line of the stack trace passed into the
+filter subroutine is a composite drawn from two different frames of
+the Perl stack trace, C<$iSubFrame> and C<$iLineFrame>. This
+composition is necessary because the Perl stack trace contains the
+subroutine that was called at C<$sFile>, C<$iLine> rather than the
+subroutine that I<contains> C<$sFile>,C<$iLine>.
+
+The subroutine returns 0 or any other false value if the stack frame
+should be omitted. It returns to 1 accept the default stack frame as
+is. If it accepts the stack frame but wants to insert extra data
+in the frame, it returns
+C<[$sFile,$iLine,$sSub,$aArgs, $extra1, $extra2, ...]>
+
+The extra data is always placed at the end after the C<$aArgs>
+member.
+
+=head3 Stack trace filtering
+
+To avoid noise, by default, intermediate frames that are associated
+with a block of code within a subroutine other than an anonymous
+sub (e.g. the frame created by C<eval {...} or do {...} >) are
+omitted from the stack trace.
+
+These omissions add to readability for most debugging purposes.
+In most cases one just wants to see which subroutine called which
+other subroutine. Frames created by eval blocks don't provide
+useful information for that purpose and simply clutter up the
+debugging output.
+
+However, there are situations where one either wants more or less
+stack trace filtering. Stack filtering can turned on or off or
+customized by setting C<$Exception::Lite::FILTER> to any of the
+following values:
+
+Normally the filtering rule is set at the start of the program or
+via the command line. It can also be set anywhere in code, with one
+caveat: an error handling block.
+
+=over
+
+=item 0
+
+Turns all filtering off so that you see each and every frame
+in the stack trace.
+
+=item 1
+
+Turns on filtering of eval frames only (default)
+
+=item C<[ regex1, regex2, ... ]>
+
+A list of regexes. If the fully qualified subroutine name matches
+any one of these regular expressions it will be omitted from the
+stack trace.
+
+=item C<$regex>
+
+A single regular expression. If the fully qualified subroutine name
+matches this regular expression, it will be omitted from the stack
+trace.
+
+=item C<$codeReference>
+
+The address of a named or anonymous routine that returns a boolean
+value: true if the frame should be includeed, false if it should be
+omitted. For parameters and return value of this subroutine see
+L</Adding information to the stack trace>.
+
+
+=back
+
+If filtering strategies change and an exception is chained, some of
+its stack frames might be lost during the chaining process if the
+filtering strategy that was in effect when the exception was
+generated changes before it is chained to another exception.
+
+
+=head2 Subclassing
+
+To declare a subclass with custom data and methods, use a three step
+process:
+
+=over
+
+=item *
+
+choose an exception superclass. The choice of superclass follows
+the rule, "like gives birth to like". Exception superclasses that
+have formats must have a superclass that also takes a format.
+Exception subclasses that have no format, must use an exception.
+
+=item *
+
+call C<declareExceptionClass> with its C<$bCustom> parameter set
+to 1
+
+=item *
+
+define a C<_new(...)> method (note the leading underscore _) and
+subclass specific methods in a block that sets the package to
+the subclass package.
+
+=back
+
+
+When the C<$bCustom> flag is set to true, it might be best to think
+of C<declareExceptionClass> as something like C<use base> or
+C<use parent> except that there is no implicit BEGIN block. Like
+both these methods it handles all of the setup details for the
+class so that you can focus on defining methods and functionality.
+
+Wnen C<Exception::Lite> sees the C<$bCustom> flag set to true, it
+assumes you plan on customizing the class. It will set up inhertance,
+and generate all the usual method definition for an C<Exception::Lite>
+class. However, on account of C<$bCustom> being true, it will add a
+few extra things so that and your custom code can play nicely
+together:
+
+=over
+
+=item *
+
+a special hash reserved for your subclsses data. You can get
+access to this hash by calling C<_p_getSubclassData()>. You are
+free to add, change, or remove entries in the hash as needed.
+
+=item *
+
+at the end of its C<new()> method, it calls
+C<< $sClass->_new($self) >>. This is why you must define a C<_new()>
+method in your subclass package block. The C<_new> method is
+responsible for doing additional setup of exception data. Since
+this method is called last it can count on all of the normally
+expected methods and data having been set up, including the
+stack trace and the message generated by the classes format rule
+(if there is one).
+
+=back
+
+For example, suppose we want to define a subclass that accepts
+formats:
+
+ #define a superclass that accepts formats
+
+ declareExceptionClass('AnyError'
+ , ['Unexpected exception: %s','exception']);
+
+
+ # declare Exception subclass
+
+ declareExceptionClass('TimedException', 'AnyError', $aFormatData,1);
+ {
+ package TimedException;
+
+ sub _new {
+ my $self = $_[0]; #exception object created by Exception::Lite
+
+ # do additional setup of properties here
+ my $timestamp=time();
+ my $hMyData = $self->_p_getSubclassData();
+ $hMyData->{when} = time();
+ }
+
+ sub getWhen {
+ my $self=$_[0];
+ return $self->_p_getSubclassData()->{when};
+ }
+ }
+
+
+Now suppose we wish to extend our custom class further. There is
+no difference in the way we do things just because it is a subclass
+of a customized C<Exception::Lite> class:
+
+ # extend TimedException further so that it
+ #
+ # - adds two additional bits of data - the effective gid and uid
+ # at the time the exception was thrown
+ # - overrides getMessage() to include the time, egid, and euid
+
+ declareExceptionClass('SecureException', 'TimedException'
+ , $aFormatData,1);
+ {
+ package TimedException;
+
+ sub _new {
+ my $self = $_[0]; #exception object created by Exception::Lite
+
+ # do additional setup of properties here
+ my $timestamp=time();
+ my $hMyData = $self->_p_getSubclassData();
+ $hMyData->{euid} = $>;
+ $hMyData->{egid} = $);
+ }
+
+ sub getEuid {
+ my $self=$_[0];
+ return $self->_p_getSubclassData()->{euid};
+ }
+ sub getEgid {
+ my $self=$_[0];
+ return $self->_p_getSubclassData()->{egid};
+ }
+ sub getMessage {
+ my $self=$_[0];
+ my $sMsg = $self->SUPER::getMessage();
+ return sprintf("%s at %s, euid=%s, guid=%s", $sMsg
+ , $self->getWhen(), $self->getEuid(), $self->getGuid());
+ }
+ }
+
+=head2 Converting other exceptions into Exception::Lite exceptions
+
+If you decide that you prefer the stack traces of this package, you
+can temporarily force all exceptions to use the C<Exception::Lite>
+stack trace, even those not generated by your own code.
+
+There are two ways to do this:
+
+* production code: chaining/wrapping
+
+* active debugging: die/warn handlers
+
+
+=head3 Wrapping and chaining
+
+The preferred solution for production code is wrapping and/or
+chaining the exception. Any non-string exception, even one
+of a class not created by C<Exception::Lite> can be chained
+to an C<Exception::Lite> exception.
+
+To chain a string exception, you first need to wrap it in
+an exception class. For this purpose you can create a special
+purpose class or use the generic exception class provided by
+the C<Exception::Lite> module: C<Exception::Lite::Any>.
+
+If you don't want to chain the exception, you can also just
+rethrow the wrapped exception, as is. Some examples:
+
+ #-----------------------------------------------------
+ # define some classes
+ #-----------------------------------------------------
+
+ # no format rule
+ declareExceptionClass('HouseholdRepairNeeded');
+
+ # format rule
+ declareExceptionClass('ProjectDelay'
+ , ['The project was delayed % days', qw(days)]);
+
+ #-----------------------------------------------------
+ # chain and/or wrap some exceptins
+ #-----------------------------------------------------
+
+ eval {
+ .... some code here ...
+ return 1;
+ } or do {
+
+ my $e=$@;
+ if (Exception::Lite::isChainable($e)) {
+ if ("$e" =~ /project/) {
+
+ # chain formatted message
+ die 'ProjectDelay'->new($e, days => 3);
+
+ } elsif ("$e" =~ /water pipe exploded/) {
+
+ # chain unformatted message
+ die 'HouseholdRepairNeeded'->new($e, 'Call the plumber');
+
+ }
+ } elsif ($e =~ 'repairman') { #exception is a string
+
+ # wrapping a scalar exception so it has the stack trace
+ # up to this point, but _no_ chaining
+ #
+ # since the exception is a scalar, the constructor
+ # of a no-format exception class will treat the first
+ # parameter as a message rather than a chained exception
+
+ die 'HouseholdRepairNeeded'->new($e);
+
+ } else {
+
+ # if we do want to chain a string exception, we need to
+ # wrap it first in an exception class:
+
+ my $eWrapped = Exception::Lite::Any->new($e);
+ die 'HouseholdRepairNeeded'
+ ->new($eWrapped, "Call the repair guy");
+ }
+ }
+
+=head3 Die/Warn Handlers
+
+Die/Warn handlers provide a quick and dirty way to at Exception::Lite
+style stack traces to all warnings and exceptions. However,
+it should ONLY BE USED DURING ACTIVE DEBUGGING. They should never
+be used in production code. Setting these handlers
+can interfere with the debugging style and techiniques of other
+programmers and that is not nice.
+
+However, so long as you are actiely debugging, setting a die or
+warn handler can be quite useful, especially if a third party module
+is generating an exception or warning and you have no idea where it
+is coming from.
+
+To set a die handler, you pass your desired stringify level or
+code reference to C<onDie>:
+
+ Exception::Lite::onDie(4);
+
+This is roughly equivalent to:
+
+ $SIG{__DIE__} = sub {
+ $Exception::Lite::STRINGIFY=4;
+ warn 'Exception::Lite::Any'->new('Unexpected death:'.$_[0])
+ unless ($^S || Exception::Lite::isException($_[0]));
+ };
+
+To set a warning handler, you pass your desired stringify level or
+code reference to C<onWarn>:
+
+ Exception::Lite::onWarn(4);
+
+This is roughly equivalent to:
+
+ $SIG{__WARN__} = sub {
+ $Exception::Lite::STRINGIFY=4;
+ print STDERR 'Exception::Lite::Any'->new("Warning: $_[0]");
+ };
+
+Typically these handlers are placed at the top of a test script
+like this:
+
+ use strict;
+ use warnings;
+ use Test::More tests => 25;
+
+ use Exception::Lite;
+ Exception::Lite::onDie(4);
+ Exception::Lite::onWarn(3);
+
+ ... actual testing code here ...
+
+
+
+=head1 WHY A NEW EXCEPTION CLASS
+
+Aren't there enough already? Well, no. This class differs from
+existing classes in several significant ways beyond "lite"-ness.
+
+=head2 Simplified integration of properties and messages
+
+C<Exception::Lite> simplifies the creation of exceptions by
+minimizing the amount of metadata that needs to be declared for
+each exception and by closely integrating exception properties
+and error messages. Though there are many exception modules
+that let you define message and properties for exceptions, in
+those other modules you have to manually maintain any connection
+between the two either in your code or in a custom subclass.
+
+In L<Exception::Class|Exception::Class>, for example, you have to
+do something like this:
+
+ #... at the start of your code ...
+ # notice how exception definition and message format
+ # string constant are in two different places and need
+ # to be manually coordinated by the programmer.
+
+ use Exception::Class {
+ 'Exception::Copy::Mine' {
+ fields => [qw(from to)];
+ }
+ # ... lots of other exceptions here ...
+ }
+ my $MSG_COPY='Could not copy A.txt to B.txt";
+
+ ... later on when you throw the exception ...
+
+ # notice the repetition in the use of exception
+ # properties; the repetition is error prone and adds
+ # unnecessary extra typing
+
+ my $sMsg = sprintf($MSG_COPY, 'A.txt', 'B.txt');
+ Exception::Copy::Mine->throw(error => $sMsg
+ , from => 'A.txt'
+ , to => 'B.txt');
+
+
+C<Exception::Lite> provides a succinct and easy to maintain
+method of declaring those same exceptions
+
+ # the declaration puts the message format string and the
+ # class declaration together for the programmer, thus
+ # resulting in less maintenence work
+
+ declareExceptionClass("Exception::Mine::Copy"
+ , ["Could not copy %s to %s", qw(from, to) ]);
+
+
+ .... some where else in your code ...
+
+
+ # there is no need to explicitly call sprintf or
+ # repetitively type variable names, nor even remember
+ # the order of parameters in the format string or check
+ # for undefined values. Both of these will produce
+ # the same error message:
+ # "Could not copy A.txt to B.txt"
+
+ die "Exception::Mine:Copy"->new(from =>'A.txt', to=>'B.txt');
+ die "Exception::Mine:Copy"->new(to =>'B.txt', from=>'A.txt');
+
+ # and this will politely fill in 'undef' for the
+ # property you leave out:
+ # "Could not copy A.txt to <undef>"
+
+ die "Exception::Mine::Copy"->new(from=>'A.txt');
+
+
+=head2 More intelligent stack trace
+
+The vast majority, if not all, of the exception modules on CPAN
+essentially reproduce Carp's presentation of the stack trace. They
+sometimes provide parameters to control the level of detail, but
+make only minimal efforts, if any, to improve on the quality of
+debugging information.
+
+C<Exception::Lite> improves on the traditional Perl stack trace
+provided by Carp in a number of ways.
+
+=over
+
+=item *
+
+Error messages are shown in full and never truncated (a problem with
+ C<Carp::croak>.
+
+=item *
+
+The ability to see a list of what called what without the clutter
+ of subroutine parameters.
+
+=item *
+
+The ability to see the context of a line that fails rather than
+a pinhole snapshot of the line itself. Thus one sees
+"at file Foo.pm, line 13 in sub doTheFunkyFunk" rather
+ than the contextless stack trace line displayed by nearly every,
+ if not all Perl stacktraces, including C<Carp::croak>:
+ "called foobar(...) at line 13 in Foo.pm".
+ When context rather than line snapshots
+ are provided, it is often enough simply to scan the list of what
+ called what to see where the error occurred.
+
+=item *
+
+Automatic filtering of stack frames that do not show the actual
+Flow from call to call. Perl internally creates stack frames for
+each eval block. Seeing these in the stack trace make it harder
+to scan the stack trace and see what called what.
+
+=item *
+
+The automatic filtering can be turned off or, alternatively
+customized to include/exclude arbitrary stack frames.
+
+=item *
+
+One can chain together exceptions and then print out what exception
+triggered what other exception. Sometimes what a low level module
+considers important about an exception is not what a higher level
+module considers important. When that happens, the programmer can
+create a new exception with a more relevant error message that
+"remembers" the exception that inspired it. If need be, one can
+see the entire history from origin to destination.
+
+=back
+
+The "traditional" stack trace smushes together all parameters into
+a single long line that is very hard to read. C<Exception::Lite>
+provides a much more readable parametr listing:
+
+=over
+
+=item *
+
+They are displayed one per line so that they can be easily read
+ and distinguished one from another
+
+=item *
+
+The string value <i>and</i> the normal object representation is
+ shown when an object's string conversion is overloaded. That way
+ there can be no confusion about whether the actual object or a
+ string was passed in as a parameter.
+
+=item *
+
+It doesn't pretend that these are the parameters passed to the
+ subroutine. It is impossible to recreate the actual values in
+ the parameter list because the parameter list for any sub is
+ just C<@_> and that can be modified when a programmer uses shift
+ to process command line arguments. The most Perl can give (through
+ its DB module) is the way C<@_> looked at the time the next frame
+ in the stack was set up. Instead of positioning the parameters
+ as if they were being passed to the subroutine, they are listed
+ below the stacktrace line saying "thrown at in line X in
+ subroutine Y". In reality, the "parameters" are the value of
+ @_ passed to subroutine Y (or @ARGV if this is the entry point),
+ or what was left of it when we got to line X.
+
+=item
+
+A visual hint that leading C<undef>s in C<@_> or C<@ARGV> may be
+ the result of shifts rather than a heap of C<undef>s passed into
+ the subroutine. This lets the programmer focus on the code, not
+ on remembering the quirks of Perl stack tracing.
+
+=back
+
+=head1 CLASS REFERENCE
+
+=head2 Class factory methods
+
+=head3 C<declareExceptionClass>
+
+ declareExceptionClass($sClass);
+ declareExceptionClass($sClass, $sSuperclass);
+ declareExceptionClass($sClass, $sSuperclass, $bCustom);
+
+ declareExceptionClass($sClass, $aFormatRule);
+ declareExceptionClass($sClass, $sSuperclass, $aFormatRule);
+ declareExceptionClass($sClass, $sSuperclass, $aFormatRule
+ , $bCustom);
+
+Generates a lightweight class definition for an exception class. It
+returns the name of the created class, i.e. $sClass.
+
+=over
+
+=item C<$sClass>
+
+The name of the class (package) to be created. Required.
+
+Any legal Perl package name may be used, so long as it hasn't
+already been used to define an exception or any other class.
+
+=item C<$sSuperclass>
+
+The name of the superclass of C<$sClass>. Optional.
+
+If missing or undefed, C<$sClass> will be be a base class
+whose only superclass is C<UNIVERSAL>, the root class of all Perl
+classes. There is no special "Exception::Base" class that all
+exceptions have to descend from, unless you want it that way
+and choose to define your set of exception classes that way.
+
+=item C<$aFormatRule>
+
+An array reference describing how to use properties to construct
+a message. Optional.
+
+If provided, the format rule is essential the same parameters as
+used by sprintf with one major exception: instead of using actual
+values as arguments, you use property names, like this:
+
+ # insert value of 'from' property in place of first %s
+ # insert value of 'to' property in place of first %s
+
+ [ 'Cannot copy from %s to %s, 'from', 'to' ]
+
+When a format rule is provided, C<Exception::Lite> will auto-generate
+the message from the properties whenever the properties are set or
+changed. Regeneration is a lightweight process that selects property
+values from the hash and sends them to C<sprintf> for formatting.
+
+Later on, when you are creating exceptions, you simply pass in the
+property values. They can be listed in any order and extra properties
+that do not appear in the message string can also be provided. If
+for some reason the value of a property is unknown, you can assign it
+C<undef> and C<Exception::Lite> will politely insert a placeholder
+for the missing value. All of the following are valid:
+
+
+ # These all generate "Cannot copy A.txt to B.txt"
+
+ $sClass->new(from => 'A.txt', to => 'B.txt');
+ $sClass->new(to => 'B.txt', from => 'A.txt');
+ $sClass->new(to => 'B.txt', from => 'A.txt'
+ , reason => 'source doesn't exist'
+ , seriousness => 4
+ );
+ $sClass->new(reason => 'source doesn't exist'
+ , seriousness => 4
+ , to => 'B.txt', from => 'A.txt'
+ );
+
+ # These generate "Cannot copy A.txt to <undef>"
+
+ $sClass->new(from => 'A.txt');
+ $sClass->new(from => 'A.txt', to => 'B.txt');
+
+=item C<$bCustom>
+
+True if the caller intends to add custom methods and/or a custom
+constructor to the newly declared class. This will force the
+L<Excepton::Lite> to generate some extra methods and data so
+that the subclass can have its own private data area in the class.
+See L</Subclassing> for more information.
+
+
+=back
+
+=head2 Object construction methods
+
+=head3 C<new>
+
+ # class configured for no generation from properties
+
+ $sClass->new($sMsg);
+ $sClass->new($sMsg,$prop1 => $val1, ....);
+ $sClass->new($e);
+ $sClass->new($e, $sMsg);
+ $sClass->new($e, $sMsg,$prop1 => $val1, ....);
+
+ # class configured to generate messages from properties
+ # using a per-class format string
+
+ $sClass->new($prop1 => $val1, ....);
+ $sClass->new($e, $prop1 => $val1, ....);
+
+
+Creates a new instance of exception class C<$sClass>. The exception
+may be independent or chained to the exception that triggered it.
+
+=over
+
+=item $e
+
+The exception that logically triggered this new exception.
+May be omitted or left undefined. If defined, the new exception is
+considered chained to C<$e>.
+
+=item $sMsg
+
+The message text, for classes with no autogeneration from properties,
+that is, classes declared like
+
+ declareExceptionClass($sClass);
+ declareExceptionClass($sClass, $sSuperclass);
+
+In the constructor, C< $sClass->new($e) >>, the message defaults to
+the message of C<$e>. Otherwise the message is required for any
+class that id declared in the above two ways.
+
+=item $prop1 => $val1
+
+The first property name and its associated value. There can be
+as many repetitions of this as there are properties. All types
+of exception classes may have property lists.
+
+=back
+
+If you have chosen to have the message be completely independent
+of properties:
+
+ declareExceptionClass('A');
+
+ # unchained exception - print output "Hello"
+
+ my $e1 = A->new("Hello", importance => 'small', risk => 'large');
+ print "$e1\n";
+
+ # chained exception - print output "Hello"
+
+ my $e2 = A->new($e1,'Goodbye');
+
+ $e2->getChained(); # returns $e1
+ print $e1->getMessage(); # outputs "Goodbye"
+ print $e1; # outputs "Goodbye"
+ print $e2->getChained()->getMessage(); # outputs "Hello"
+
+
+If you have chosen to have the message autogenerated from properties
+your call to C<new> will look like this:
+
+ $sFormat ='the importance is %s, but the risk is %s';
+ declareExceptionClass('B', [ $sFormat, qw(importance risk)]);
+
+
+ # unchained exception
+
+ my $e1 = B->new(importance=>'small', risk=>'large');
+
+ $e1->getChained(); # returns undef
+ print "$e1\n"; # outputs "The importance is small, but the
+ # risk is large"
+
+ # chained exception
+
+ $e2 = B->new($e1, importance=>'yink', risk=>'hooboy');
+ $e2->getChained(); # returns $e1
+ "$e2" # evaluates to "The importance is yink, but
+ # the risk is hooboy"
+ $e2->getMessage() # same as "$e2"
+ $e2->getChained()->getMessage(); # same as "$e1"
+
+
+
+=head2 Object methods
+
+=head3 C<getMessage>
+
+ $e->getMessage();
+
+Returns the messsage, i.e. the value displayed when this exception
+is treated as a string. This is the value without line numbers
+stack trace or other information. It includes only the format
+string with the property values inserted.
+
+=head3 C<getProperty>
+
+ $e->getProperty($sName);
+
+Returns the property value for the C<$sName> property.
+
+=head3 C<isProperty>
+
+ $e->isProperty($sName)
+
+Returns true if the exception has the C<$sName> property, even if
+the value is undefined. (checks existance, not definition).
+
+=head3 C<getPid>
+
+ $e->getPid();
+
+Returns the process id of the process where the exception was
+thrown.
+
+=head3 C<getPackage>
+
+ $e->getPackage();
+
+Returns the package contining the entry point of the process, i.e.
+the package identified at the top of the stack.
+
+
+=head3 C<getTid>
+
+Returns the thread where the exception was thrown.
+
+ $e->getTid();
+
+=head3 C<getStackTrace>
+
+ $e->getStackTrace();
+
+Returns the stack trace from the point where the exception was
+thrown (frame 0) to the entry point (frame -1). The stack trace
+is structured as an array of arrays (AoA) where each member array
+represents a single lightweight frame with four data per frame:
+
+ [0] the file
+ [1] the line number within the file
+ [2] the subroutine where the exception was called. File and
+ line number will be within this subroutine.
+ [3] a comma delimited string containing string representations
+ of the values that were stored in @_ at the time the
+ exception was thrown. If shift was used to process the
+ incoming subroutine arguments, @_ will usually contain
+ several leading undefs.
+
+For more information about each component of a stack frame, please
+see the documentation below for the following methods:
+
+* C<getFile> - explains what to expect in [0] of stack frame
+
+* C<getLine> - explains what to expect in [1] of stack frame
+
+* C<getSubroutine> - explains what to expect in [2] of stack frame
+
+* C<getArgs> - explains what to expect in [3] of stack frame
+
+The frame closest to the thrown exception is numbered 0. In fact
+frame 0, stores information about the actual point where the exception
+was thrown.
+
+
+=head3 C<getFrameCount>
+
+ $e->getFrameCount();
+
+Returns the number of frames in the stack trace.
+
+=head3 C<getFile>
+
+ $e->getFile(0); # gets frame where exception was thrown
+ $e->getFile(-1); # gets entry point frame
+
+ $e->getFile(); # short hand for $e->getFile(0)
+ $e->getFile($i);
+
+Without an argument, this method returns the name of the file where
+the exception was thrown. With an argument it returns the name of
+the file in the C<$i>th frame of the stack trace.
+
+Negative values of C<$i> will be counted from the entry point with
+C<-1> representing the entry point frame, C<-2> representing the
+first call made within the script and so on.
+
+=head3 C<getLine>
+
+ $e->getLine(0); # gets frame where exception was thrown
+ $e->getLine(-1); # gets entry point frame
+
+ $e->getLine(); # short hand for $e->getLine(0)
+ $e->getLine($i);
+
+Without an argument, this method returns the line number where the
+exception was thrown. With an argument it returns the line number
+in the C<$i>th frame of the stack trace.
+
+Negative values of C<$i> will be counted from the entry point with
+C<-1> representing the entry point frame, C<-2> representing the
+first call made within the script and so on.
+
+=head3 C<getSubroutine>
+
+ $e->getSubroutine(0); # gets frame where exception was thrown
+ $e->getSubroutine(-1); # gets entry point frame
+
+ $e->getSubroutine(); # short hand for $e->getSubroutine(0)
+ $e->getSubroutine($i);
+
+Without an argument, this method returns the name of the subroutine
+where this exception was created via C<new(...)>. With an argument
+it returns the value of the subroutine (or package entry point) in
+the C<$i>th frame of the stack trace.
+
+Negative values of C<$i> will be counted from the entry point with
+C<-1> representing the entry point frame, C<-2> representing the
+first call made within the script and so on.
+
+Note: This is not the same value as returned by C<caller($i)>. C<caller> returns the name of the subroutine that was being called
+at the time of death rather than the containing subroutine.
+
+The subroutine name in array element [2] includes the package name
+so it will be 'MyPackage::Utils::doit' and not just 'doit'. In the
+entry point frame there is, of course, no containing subroutine so
+the value in this string is instead the package name embedded in
+the string "<package: packageName>".
+
+
+=head3 C<getArgs>
+
+ $e->getArgs(0); # gets frame where exception was thrown
+ $e->getArgs(-1); # gets entry point frame
+
+ $e->getArgs(); # short hand for $e->getArgs(0)
+ $e->getArgs($i);
+
+Without an argument, this method returns the value of C<@_> (or
+C<@ARGV> for an entry point frame) at the time the exception was
+thrown. With an argument it returns the name of
+the file in the C<$i>th frame of the stack trace.
+
+Negative values of C<$i> will be counted from the entry point with
+C<-1> representing the entry point frame, C<-2> representing the
+first call made within the script and so on.
+
+ @_, is the best approximation Perl provides for the arguments
+used to call the subroutine. At the start of the subroutine it does
+in fact reflect the parameters passed in, but frequently programmers
+will process this array with the C<shift> operator which will set
+leading arguments to C<undef>. The debugger does not cache the
+oiginal value of @_, so all you can get from its stack trace is the
+value at the time the exception was thrown, not the value when the
+subroutine was entered.
+
+=head3 C<getPropagation>
+
+ $e->getPropagation();
+
+Returns an array reference with one element for each time this
+exception was caught and rethrown using either Perl's own rethrow
+syntax C<$@=$e; die;> or this packages: C<< die->rethrow() >>.
+
+Each element of the array contains a file and line number where
+the exception was rethrown:
+
+ [0] file where exception was caught and rethrown
+ [1] line number where the exception was caught and rethrown
+
+Note: do not confuse the stack trace with propagation. The stack
+trace is the sequence of calls that were made I<before> the
+exception was thrown. The propagation file and line numbers
+refer to where the exception was caught in an exception handling
+block I<after> the exception was thrown.
+
+Generally, bad data is the reason behind an exception. To see
+where the bad data came from, it is generally more useful to
+look at the stack and see what data was passed down to the point
+where the exception was generated than it is to look at where
+the exception was caught after the fact.
+
+=head3 C<getChained>
+
+ my $eChained = $e->getChained();
+
+Returns the chained exception, or undef if the exception is not
+chained. Chained exceptions are created by inserting the triggering
+exception as the first parameter to C<new(...)>.
+
+ # class level format
+ MyException1->new(reason=>'blahblahblah'); #unchained
+ MyException1->new($e, reason=>'blahblahblah'); #chained
+
+ # no format string
+ MyException1->new('blahblahblah'); #unchained
+ MyException1->new($e, reason=>'blahblahblah'); #chained
+
+
+The chained exception can be a reference to any sort of data. It
+does not need to belong to the same class as the new exception,
+nor does it even have to belong to a class generated by
+C<Exception::Lite>. Its only restriction is that it may not be
+a scalar(string, number, ec). To see if an exception
+may be chained you can call C<Exception::Lite::isChainable()>:
+
+ if (Exception::Lite::isChainable($e)) {
+ die MyException1->new($e, reason=>'blahblahblah');
+ } else {
+
+ # another alternative for string exceptions
+ my $eWrapper=MyWrapperForStringExceptions->new($e);
+ die MyException1->new($eWrapper, reason=>'blahblahblah');
+
+ # another alternative for string exceptions
+ die MyException1->new($eWrapper, reason=>"blahblahblah: $e");
+ }
+
+
+=head3 C<rethrow>
+
+ $e->rethrow();
+ $e->rethrow($prop => $newValue); # format rule
+
+ $e->rethrow($newMsg, $p1 => $newValue); # no format rule
+ $e->rethrow(undef, $pl => $newValue); # no format rule
+ $e->rethrow($sNewMsg); # no format rule
+
+
+Propagates the exception using the method (C<PROPAGATE>) as would
+be called were one to use Perl's native 'rethrow' syntax,
+C<$@=$e; die>.
+
+The first form with no arguments simply rethrows the exception.
+The remain formats let one override property values and/or update
+the message. The argument list is the same as for C<new> except
+that exceptions with no or object level format strings may have
+an undefined message.
+
+For class format exceptions, the message will automatically be
+updated if any of the properties used to construct it have changed.
+
+For exception classes with no formatting, property and message
+changes are independent of each other. If C<$sMsg> is set to C<undef>
+the properties will be changed and the message will be left alone.
+If C<$sMsg> is provided, but no override properties are provided,
+the message will change but the properties will be left untouched.
+
+=head3 C<_p_getSubclassData>
+
+Method for internal use by custom subclasses. This method retrieves
+the data hash reserved for use by custom methods.
+
+
+=head1 SEE ALSO
+
+=head2 Canned test modules
+
+Test modules for making sure your code generates the right
+exceptions. They work with any OOP solution, even C<Exception::Lite>
+
+* L<Test::Exception|Test::Exception> - works with any OOP solution
+
+* L<Test::Exception::LessClever|Test::Exception::LessClever> - works
+ with any OOP solution
+
+=head2 Alternate OOP solutions
+
+=head3 L<Exception::Class|Exception::Class>
+
+This module has a fair number of non-core modules. There are several
+extension modules. Most are adapter classes that convert exceptions
+produced by popular CPAN modules into Exception::Class modules:
+
+* L<Exception::Class::Nested|Exception::Class::Nested> - changes
+ the syntax for declaring exceptions.
+
+* L<MooseX::Error::Exception::Class|MooseX::Error::Exception::Class>
+ - converts Moose exceptions to
+ C<Exception::Class> instances.
+
+* L<HTTP::Exception|HTTP::Exception> - wrapper around HTTP exceptions
+
+* L<Mail::Log::Exceptions|Mail::Log::Exceptions> - wrapper around
+ Mail::Log exceptions
+
+* L<Exception::Class::DBI|Exception::Class::DBI> - wrapper around
+ DBI exceptions
+
+* L<Error::Exception|Error::Exception> - prints out exception
+ properties as part of exception stringification.
+
+It takes a heavy approach to OOP, requiring all properties to be
+predeclared. It also stores a lot of information about an exception,
+not all of which is likely to be important to the average user, e.g.
+pid, uid, guid and even the entire stack trace.
+
+There is no support for auto-generating messages based on
+properties.
+
+For an extended discussion of C<Exception::Class>, see
+L<http://www.drdobbs.com/web-development/184416129>.
+
+=head3 L<Exception::Base|Exception::Base>
+
+A light weight version of L<Exception::Class|Exception::Class>.
+Uses only core modules but is fairly new and has no significant
+eco-system of extensions (yet).
+Like C<Exception::Class> properties must be explicitly declared and
+there is no support for autogenerating messages based on properties.
+
+
+=head3 L<Class::Throwable|Class::Throwable>
+
+Another light weight version of L<Exception::Class|Exception::Class>.
+Unlike C<Exception::Class> you can control the amount of system
+state and stack trace information stored at the time an exception
+is generated.
+
+=head2 Syntactic sugar solutions
+
+Syntactical sugar solutions allow java like try/catch blocks to
+replace the more awkward C<die>, C<eval/do>, and C<$@=$e; die>
+pattern. Take care in chosing these methods as they sometimes
+use coding strategies known to cause problems:
+
+=over
+
+=item *
+
+overriding signal handlers - possible interference with your own
+code or third party module use of those handlers.
+
+=item *
+
+source code filtering - can shift line numbers so that the reported
+line number and the actual line number may not be the same.
+
+=item *
+
+closures - there is a apparently a problem with nested closures
+causing memory leaks in some versions of Perl (pre 5.8.4). This
+has been since fixed since 5.8.4.
+
+=back
+
+Modules providing syntactic sugar include:
+
+* L<Try::Catch|Try::Catch>
+
+* L<Try::Tiny|Try::Tiny>
+
+* C<Error|Error>
+
+* L<Exception::Caught|Exception::Caught>
+
+* L<Exception::SEH|Exception::SEH>
+
+* C<Exception|Exception>
+
+* L<Exception::Class::TryCatch|Exception::Class::TryCatch> - extension of L<Exception::Class|Exception::Class>
+
+* L<Exception::Class::TCF|Exception::Class::TCF> - extension of L<Exception::Class|Exception::Class>
+
+
+=head1 EXPORTS
+
+No subroutines are exported by default. See the start of the synopsis
+for optional exports.
+
+
+=head1 AUTHOR
+
+Elizabeth Grace Frank-Backman
+
+=head1 COPYRIGHT
+
+Copyright (c) 2011 Elizabeth Grace Frank-Backman.
+All rights reserved.
+
+=head1 LICENSE
+
+This program is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.