perl / private-Error.pmon commit rebase -i --autosquash: auto-squash commits (f59baa5)
   1# Error.pm
   2#
   3# Copyright (c) 1997-8 Graham Barr <gbarr@ti.com>. All rights reserved.
   4# This program is free software; you can redistribute it and/or
   5# modify it under the same terms as Perl itself.
   6#
   7# Based on my original Error.pm, and Exceptions.pm by Peter Seibel
   8# <peter@weblogic.com> and adapted by Jesse Glick <jglick@sig.bsh.com>.
   9#
  10# but modified ***significantly***
  11
  12package Error;
  13
  14use strict;
  15use vars qw($VERSION);
  16use 5.004;
  17
  18$VERSION = "0.15009";
  19
  20use overload (
  21        '""'       =>   'stringify',
  22        '0+'       =>   'value',
  23        'bool'     =>   sub { return 1; },
  24        'fallback' =>   1
  25);
  26
  27$Error::Depth = 0;      # Depth to pass to caller()
  28$Error::Debug = 0;      # Generate verbose stack traces
  29@Error::STACK = ();     # Clause stack for try
  30$Error::THROWN = undef; # last error thrown, a workaround until die $ref works
  31
  32my $LAST;               # Last error created
  33my %ERROR;              # Last error associated with package
  34
  35sub throw_Error_Simple
  36{
  37    my $args = shift;
  38    return Error::Simple->new($args->{'text'});
  39}
  40
  41$Error::ObjectifyCallback = \&throw_Error_Simple;
  42
  43
  44# Exported subs are defined in Error::subs
  45
  46sub import {
  47    shift;
  48    local $Exporter::ExportLevel = $Exporter::ExportLevel + 1;
  49    Error::subs->import(@_);
  50}
  51
  52# I really want to use last for the name of this method, but it is a keyword
  53# which prevent the syntax  last Error
  54
  55sub prior {
  56    shift; # ignore
  57
  58    return $LAST unless @_;
  59
  60    my $pkg = shift;
  61    return exists $ERROR{$pkg} ? $ERROR{$pkg} : undef
  62        unless ref($pkg);
  63
  64    my $obj = $pkg;
  65    my $err = undef;
  66    if($obj->isa('HASH')) {
  67        $err = $obj->{'__Error__'}
  68            if exists $obj->{'__Error__'};
  69    }
  70    elsif($obj->isa('GLOB')) {
  71        $err = ${*$obj}{'__Error__'}
  72            if exists ${*$obj}{'__Error__'};
  73    }
  74
  75    $err;
  76}
  77
  78sub flush {
  79    shift; #ignore
  80
  81    unless (@_) {
  82       $LAST = undef;
  83       return;
  84    }
  85
  86    my $pkg = shift;
  87    return unless ref($pkg);
  88
  89    undef $ERROR{$pkg} if defined $ERROR{$pkg};
  90}
  91
  92# Return as much information as possible about where the error
  93# happened. The -stacktrace element only exists if $Error::DEBUG
  94# was set when the error was created
  95
  96sub stacktrace {
  97    my $self = shift;
  98
  99    return $self->{'-stacktrace'}
 100        if exists $self->{'-stacktrace'};
 101
 102    my $text = exists $self->{'-text'} ? $self->{'-text'} : "Died";
 103
 104    $text .= sprintf(" at %s line %d.\n", $self->file, $self->line)
 105        unless($text =~ /\n$/s);
 106
 107    $text;
 108}
 109
 110# Allow error propagation, ie
 111#
 112# $ber->encode(...) or
 113#    return Error->prior($ber)->associate($ldap);
 114
 115sub associate {
 116    my $err = shift;
 117    my $obj = shift;
 118
 119    return unless ref($obj);
 120
 121    if($obj->isa('HASH')) {
 122        $obj->{'__Error__'} = $err;
 123    }
 124    elsif($obj->isa('GLOB')) {
 125        ${*$obj}{'__Error__'} = $err;
 126    }
 127    $obj = ref($obj);
 128    $ERROR{ ref($obj) } = $err;
 129
 130    return;
 131}
 132
 133sub new {
 134    my $self = shift;
 135    my($pkg,$file,$line) = caller($Error::Depth);
 136
 137    my $err = bless {
 138        '-package' => $pkg,
 139        '-file'    => $file,
 140        '-line'    => $line,
 141        @_
 142    }, $self;
 143
 144    $err->associate($err->{'-object'})
 145        if(exists $err->{'-object'});
 146
 147    # To always create a stacktrace would be very inefficient, so
 148    # we only do it if $Error::Debug is set
 149
 150    if($Error::Debug) {
 151        require Carp;
 152        local $Carp::CarpLevel = $Error::Depth;
 153        my $text = defined($err->{'-text'}) ? $err->{'-text'} : "Error";
 154        my $trace = Carp::longmess($text);
 155        # Remove try calls from the trace
 156        $trace =~ s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog;
 157        $trace =~ s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::run_clauses[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog;
 158        $err->{'-stacktrace'} = $trace
 159    }
 160
 161    $@ = $LAST = $ERROR{$pkg} = $err;
 162}
 163
 164# Throw an error. this contains some very gory code.
 165
 166sub throw {
 167    my $self = shift;
 168    local $Error::Depth = $Error::Depth + 1;
 169
 170    # if we are not rethrow-ing then create the object to throw
 171    $self = $self->new(@_) unless ref($self);
 172
 173    die $Error::THROWN = $self;
 174}
 175
 176# syntactic sugar for
 177#
 178#    die with Error( ... );
 179
 180sub with {
 181    my $self = shift;
 182    local $Error::Depth = $Error::Depth + 1;
 183
 184    $self->new(@_);
 185}
 186
 187# syntactic sugar for
 188#
 189#    record Error( ... ) and return;
 190
 191sub record {
 192    my $self = shift;
 193    local $Error::Depth = $Error::Depth + 1;
 194
 195    $self->new(@_);
 196}
 197
 198# catch clause for
 199#
 200# try { ... } catch CLASS with { ... }
 201
 202sub catch {
 203    my $pkg = shift;
 204    my $code = shift;
 205    my $clauses = shift || {};
 206    my $catch = $clauses->{'catch'} ||= [];
 207
 208    unshift @$catch,  $pkg, $code;
 209
 210    $clauses;
 211}
 212
 213# Object query methods
 214
 215sub object {
 216    my $self = shift;
 217    exists $self->{'-object'} ? $self->{'-object'} : undef;
 218}
 219
 220sub file {
 221    my $self = shift;
 222    exists $self->{'-file'} ? $self->{'-file'} : undef;
 223}
 224
 225sub line {
 226    my $self = shift;
 227    exists $self->{'-line'} ? $self->{'-line'} : undef;
 228}
 229
 230sub text {
 231    my $self = shift;
 232    exists $self->{'-text'} ? $self->{'-text'} : undef;
 233}
 234
 235# overload methods
 236
 237sub stringify {
 238    my $self = shift;
 239    defined $self->{'-text'} ? $self->{'-text'} : "Died";
 240}
 241
 242sub value {
 243    my $self = shift;
 244    exists $self->{'-value'} ? $self->{'-value'} : undef;
 245}
 246
 247package Error::Simple;
 248
 249@Error::Simple::ISA = qw(Error);
 250
 251sub new {
 252    my $self  = shift;
 253    my $text  = "" . shift;
 254    my $value = shift;
 255    my(@args) = ();
 256
 257    local $Error::Depth = $Error::Depth + 1;
 258
 259    @args = ( -file => $1, -line => $2)
 260        if($text =~ s/\s+at\s+(\S+)\s+line\s+(\d+)(?:,\s*<[^>]*>\s+line\s+\d+)?\.?\n?$//s);
 261    push(@args, '-value', 0 + $value)
 262        if defined($value);
 263
 264    $self->SUPER::new(-text => $text, @args);
 265}
 266
 267sub stringify {
 268    my $self = shift;
 269    my $text = $self->SUPER::stringify;
 270    $text .= sprintf(" at %s line %d.\n", $self->file, $self->line)
 271        unless($text =~ /\n$/s);
 272    $text;
 273}
 274
 275##########################################################################
 276##########################################################################
 277
 278# Inspired by code from Jesse Glick <jglick@sig.bsh.com> and
 279# Peter Seibel <peter@weblogic.com>
 280
 281package Error::subs;
 282
 283use Exporter ();
 284use vars qw(@EXPORT_OK @ISA %EXPORT_TAGS);
 285
 286@EXPORT_OK   = qw(try with finally except otherwise);
 287%EXPORT_TAGS = (try => \@EXPORT_OK);
 288
 289@ISA = qw(Exporter);
 290
 291
 292sub blessed {
 293        my $item = shift;
 294        local $@; # don't kill an outer $@
 295        ref $item and eval { $item->can('can') };
 296}
 297
 298
 299sub run_clauses ($$$\@) {
 300    my($clauses,$err,$wantarray,$result) = @_;
 301    my $code = undef;
 302
 303    $err = $Error::ObjectifyCallback->({'text' =>$err}) unless ref($err);
 304
 305    CATCH: {
 306
 307        # catch
 308        my $catch;
 309        if(defined($catch = $clauses->{'catch'})) {
 310            my $i = 0;
 311
 312            CATCHLOOP:
 313            for( ; $i < @$catch ; $i += 2) {
 314                my $pkg = $catch->[$i];
 315                unless(defined $pkg) {
 316                    #except
 317                    splice(@$catch,$i,2,$catch->[$i+1]->());
 318                    $i -= 2;
 319                    next CATCHLOOP;
 320                }
 321                elsif(blessed($err) && $err->isa($pkg)) {
 322                    $code = $catch->[$i+1];
 323                    while(1) {
 324                        my $more = 0;
 325                        local($Error::THROWN);
 326                        my $ok = eval {
 327                            if($wantarray) {
 328                                @{$result} = $code->($err,\$more);
 329                            }
 330                            elsif(defined($wantarray)) {
 331                                @{$result} = ();
 332                                $result->[0] = $code->($err,\$more);
 333                            }
 334                            else {
 335                                $code->($err,\$more);
 336                            }
 337                            1;
 338                        };
 339                        if( $ok ) {
 340                            next CATCHLOOP if $more;
 341                            undef $err;
 342                        }
 343                        else {
 344                            $err = defined($Error::THROWN)
 345                                    ? $Error::THROWN : $@;
 346                $err = $Error::ObjectifyCallback->({'text' =>$err})
 347                    unless ref($err);
 348                        }
 349                        last CATCH;
 350                    };
 351                }
 352            }
 353        }
 354
 355        # otherwise
 356        my $owise;
 357        if(defined($owise = $clauses->{'otherwise'})) {
 358            my $code = $clauses->{'otherwise'};
 359            my $more = 0;
 360            my $ok = eval {
 361                if($wantarray) {
 362                    @{$result} = $code->($err,\$more);
 363                }
 364                elsif(defined($wantarray)) {
 365                    @{$result} = ();
 366                    $result->[0] = $code->($err,\$more);
 367                }
 368                else {
 369                    $code->($err,\$more);
 370                }
 371                1;
 372            };
 373            if( $ok ) {
 374                undef $err;
 375            }
 376            else {
 377                $err = defined($Error::THROWN)
 378                        ? $Error::THROWN : $@;
 379
 380        $err = $Error::ObjectifyCallback->({'text' =>$err})
 381            unless ref($err);
 382            }
 383        }
 384    }
 385    $err;
 386}
 387
 388sub try (&;$) {
 389    my $try = shift;
 390    my $clauses = @_ ? shift : {};
 391    my $ok = 0;
 392    my $err = undef;
 393    my @result = ();
 394
 395    unshift @Error::STACK, $clauses;
 396
 397    my $wantarray = wantarray();
 398
 399    do {
 400        local $Error::THROWN = undef;
 401    local $@ = undef;
 402
 403        $ok = eval {
 404            if($wantarray) {
 405                @result = $try->();
 406            }
 407            elsif(defined $wantarray) {
 408                $result[0] = $try->();
 409            }
 410            else {
 411                $try->();
 412            }
 413            1;
 414        };
 415
 416        $err = defined($Error::THROWN) ? $Error::THROWN : $@
 417            unless $ok;
 418    };
 419
 420    shift @Error::STACK;
 421
 422    $err = run_clauses($clauses,$err,wantarray,@result)
 423        unless($ok);
 424
 425    $clauses->{'finally'}->()
 426        if(defined($clauses->{'finally'}));
 427
 428    if (defined($err))
 429    {
 430        if (blessed($err) && $err->can('throw'))
 431        {
 432            throw $err;
 433        }
 434        else
 435        {
 436            die $err;
 437        }
 438    }
 439
 440    wantarray ? @result : $result[0];
 441}
 442
 443# Each clause adds a sub to the list of clauses. The finally clause is
 444# always the last, and the otherwise clause is always added just before
 445# the finally clause.
 446#
 447# All clauses, except the finally clause, add a sub which takes one argument
 448# this argument will be the error being thrown. The sub will return a code ref
 449# if that clause can handle that error, otherwise undef is returned.
 450#
 451# The otherwise clause adds a sub which unconditionally returns the users
 452# code reference, this is why it is forced to be last.
 453#
 454# The catch clause is defined in Error.pm, as the syntax causes it to
 455# be called as a method
 456
 457sub with (&;$) {
 458    @_
 459}
 460
 461sub finally (&) {
 462    my $code = shift;
 463    my $clauses = { 'finally' => $code };
 464    $clauses;
 465}
 466
 467# The except clause is a block which returns a hashref or a list of
 468# key-value pairs, where the keys are the classes and the values are subs.
 469
 470sub except (&;$) {
 471    my $code = shift;
 472    my $clauses = shift || {};
 473    my $catch = $clauses->{'catch'} ||= [];
 474
 475    my $sub = sub {
 476        my $ref;
 477        my(@array) = $code->($_[0]);
 478        if(@array == 1 && ref($array[0])) {
 479            $ref = $array[0];
 480            $ref = [ %$ref ]
 481                if(UNIVERSAL::isa($ref,'HASH'));
 482        }
 483        else {
 484            $ref = \@array;
 485        }
 486        @$ref
 487    };
 488
 489    unshift @{$catch}, undef, $sub;
 490
 491    $clauses;
 492}
 493
 494sub otherwise (&;$) {
 495    my $code = shift;
 496    my $clauses = shift || {};
 497
 498    if(exists $clauses->{'otherwise'}) {
 499        require Carp;
 500        Carp::croak("Multiple otherwise clauses");
 501    }
 502
 503    $clauses->{'otherwise'} = $code;
 504
 505    $clauses;
 506}
 507
 5081;
 509__END__
 510
 511=head1 NAME
 512
 513Error - Error/exception handling in an OO-ish way
 514
 515=head1 SYNOPSIS
 516
 517    use Error qw(:try);
 518
 519    throw Error::Simple( "A simple error");
 520
 521    sub xyz {
 522        ...
 523        record Error::Simple("A simple error")
 524            and return;
 525    }
 526
 527    unlink($file) or throw Error::Simple("$file: $!",$!);
 528
 529    try {
 530        do_some_stuff();
 531        die "error!" if $condition;
 532        throw Error::Simple -text => "Oops!" if $other_condition;
 533    }
 534    catch Error::IO with {
 535        my $E = shift;
 536        print STDERR "File ", $E->{'-file'}, " had a problem\n";
 537    }
 538    except {
 539        my $E = shift;
 540        my $general_handler=sub {send_message $E->{-description}};
 541        return {
 542            UserException1 => $general_handler,
 543            UserException2 => $general_handler
 544        };
 545    }
 546    otherwise {
 547        print STDERR "Well I don't know what to say\n";
 548    }
 549    finally {
 550        close_the_garage_door_already(); # Should be reliable
 551    }; # Don't forget the trailing ; or you might be surprised
 552
 553=head1 DESCRIPTION
 554
 555The C<Error> package provides two interfaces. Firstly C<Error> provides
 556a procedural interface to exception handling. Secondly C<Error> is a
 557base class for errors/exceptions that can either be thrown, for
 558subsequent catch, or can simply be recorded.
 559
 560Errors in the class C<Error> should not be thrown directly, but the
 561user should throw errors from a sub-class of C<Error>.
 562
 563=head1 PROCEDURAL INTERFACE
 564
 565C<Error> exports subroutines to perform exception handling. These will
 566be exported if the C<:try> tag is used in the C<use> line.
 567
 568=over 4
 569
 570=item try BLOCK CLAUSES
 571
 572C<try> is the main subroutine called by the user. All other subroutines
 573exported are clauses to the try subroutine.
 574
 575The BLOCK will be evaluated and, if no error is throw, try will return
 576the result of the block.
 577
 578C<CLAUSES> are the subroutines below, which describe what to do in the
 579event of an error being thrown within BLOCK.
 580
 581=item catch CLASS with BLOCK
 582
 583This clauses will cause all errors that satisfy C<$err-E<gt>isa(CLASS)>
 584to be caught and handled by evaluating C<BLOCK>.
 585
 586C<BLOCK> will be passed two arguments. The first will be the error
 587being thrown. The second is a reference to a scalar variable. If this
 588variable is set by the catch block then, on return from the catch
 589block, try will continue processing as if the catch block was never
 590found.
 591
 592To propagate the error the catch block may call C<$err-E<gt>throw>
 593
 594If the scalar reference by the second argument is not set, and the
 595error is not thrown. Then the current try block will return with the
 596result from the catch block.
 597
 598=item except BLOCK
 599
 600When C<try> is looking for a handler, if an except clause is found
 601C<BLOCK> is evaluated. The return value from this block should be a
 602HASHREF or a list of key-value pairs, where the keys are class names
 603and the values are CODE references for the handler of errors of that
 604type.
 605
 606=item otherwise BLOCK
 607
 608Catch any error by executing the code in C<BLOCK>
 609
 610When evaluated C<BLOCK> will be passed one argument, which will be the
 611error being processed.
 612
 613Only one otherwise block may be specified per try block
 614
 615=item finally BLOCK
 616
 617Execute the code in C<BLOCK> either after the code in the try block has
 618successfully completed, or if the try block throws an error then
 619C<BLOCK> will be executed after the handler has completed.
 620
 621If the handler throws an error then the error will be caught, the
 622finally block will be executed and the error will be re-thrown.
 623
 624Only one finally block may be specified per try block
 625
 626=back
 627
 628=head1 CLASS INTERFACE
 629
 630=head2 CONSTRUCTORS
 631
 632The C<Error> object is implemented as a HASH. This HASH is initialized
 633with the arguments that are passed to it's constructor. The elements
 634that are used by, or are retrievable by the C<Error> class are listed
 635below, other classes may add to these.
 636
 637        -file
 638        -line
 639        -text
 640        -value
 641        -object
 642
 643If C<-file> or C<-line> are not specified in the constructor arguments
 644then these will be initialized with the file name and line number where
 645the constructor was called from.
 646
 647If the error is associated with an object then the object should be
 648passed as the C<-object> argument. This will allow the C<Error> package
 649to associate the error with the object.
 650
 651The C<Error> package remembers the last error created, and also the
 652last error associated with a package. This could either be the last
 653error created by a sub in that package, or the last error which passed
 654an object blessed into that package as the C<-object> argument.
 655
 656=over 4
 657
 658=item throw ( [ ARGS ] )
 659
 660Create a new C<Error> object and throw an error, which will be caught
 661by a surrounding C<try> block, if there is one. Otherwise it will cause
 662the program to exit.
 663
 664C<throw> may also be called on an existing error to re-throw it.
 665
 666=item with ( [ ARGS ] )
 667
 668Create a new C<Error> object and returns it. This is defined for
 669syntactic sugar, eg
 670
 671    die with Some::Error ( ... );
 672
 673=item record ( [ ARGS ] )
 674
 675Create a new C<Error> object and returns it. This is defined for
 676syntactic sugar, eg
 677
 678    record Some::Error ( ... )
 679        and return;
 680
 681=back
 682
 683=head2 STATIC METHODS
 684
 685=over 4
 686
 687=item prior ( [ PACKAGE ] )
 688
 689Return the last error created, or the last error associated with
 690C<PACKAGE>
 691
 692=item flush ( [ PACKAGE ] )
 693
 694Flush the last error created, or the last error associated with
 695C<PACKAGE>.It is necessary to clear the error stack before exiting the
 696package or uncaught errors generated using C<record> will be reported.
 697
 698     $Error->flush;
 699
 700=cut
 701
 702=back
 703
 704=head2 OBJECT METHODS
 705
 706=over 4
 707
 708=item stacktrace
 709
 710If the variable C<$Error::Debug> was non-zero when the error was
 711created, then C<stacktrace> returns a string created by calling
 712C<Carp::longmess>. If the variable was zero the C<stacktrace> returns
 713the text of the error appended with the filename and line number of
 714where the error was created, providing the text does not end with a
 715newline.
 716
 717=item object
 718
 719The object this error was associated with
 720
 721=item file
 722
 723The file where the constructor of this error was called from
 724
 725=item line
 726
 727The line where the constructor of this error was called from
 728
 729=item text
 730
 731The text of the error
 732
 733=back
 734
 735=head2 OVERLOAD METHODS
 736
 737=over 4
 738
 739=item stringify
 740
 741A method that converts the object into a string. This method may simply
 742return the same as the C<text> method, or it may append more
 743information. For example the file name and line number.
 744
 745By default this method returns the C<-text> argument that was passed to
 746the constructor, or the string C<"Died"> if none was given.
 747
 748=item value
 749
 750A method that will return a value that can be associated with the
 751error. For example if an error was created due to the failure of a
 752system call, then this may return the numeric value of C<$!> at the
 753time.
 754
 755By default this method returns the C<-value> argument that was passed
 756to the constructor.
 757
 758=back
 759
 760=head1 PRE-DEFINED ERROR CLASSES
 761
 762=over 4
 763
 764=item Error::Simple
 765
 766This class can be used to hold simple error strings and values. It's
 767constructor takes two arguments. The first is a text value, the second
 768is a numeric value. These values are what will be returned by the
 769overload methods.
 770
 771If the text value ends with C<at file line 1> as $@ strings do, then
 772this infomation will be used to set the C<-file> and C<-line> arguments
 773of the error object.
 774
 775This class is used internally if an eval'd block die's with an error
 776that is a plain string. (Unless C<$Error::ObjectifyCallback> is modified)
 777
 778=back
 779
 780=head1 $Error::ObjectifyCallback
 781
 782This variable holds a reference to a subroutine that converts errors that
 783are plain strings to objects. It is used by Error.pm to convert textual
 784errors to objects, and can be overridden by the user.
 785
 786It accepts a single argument which is a hash reference to named parameters.
 787Currently the only named parameter passed is C<'text'> which is the text
 788of the error, but others may be available in the future.
 789
 790For example the following code will cause Error.pm to throw objects of the
 791class MyError::Bar by default:
 792
 793    sub throw_MyError_Bar
 794    {
 795        my $args = shift;
 796        my $err = MyError::Bar->new();
 797        $err->{'MyBarText'} = $args->{'text'};
 798        return $err;
 799    }
 800
 801    {
 802        local $Error::ObjectifyCallback = \&throw_MyError_Bar;
 803
 804        # Error handling here.
 805    }
 806
 807=head1 KNOWN BUGS
 808
 809None, but that does not mean there are not any.
 810
 811=head1 AUTHORS
 812
 813Graham Barr <gbarr@pobox.com>
 814
 815The code that inspired me to write this was originally written by
 816Peter Seibel <peter@weblogic.com> and adapted by Jesse Glick
 817<jglick@sig.bsh.com>.
 818
 819=head1 MAINTAINER
 820
 821Shlomi Fish <shlomif@iglu.org.il>
 822
 823=head1 PAST MAINTAINERS
 824
 825Arun Kumar U <u_arunkumar@yahoo.com>
 826
 827=cut