Category: Control Flow Utilities
Author/Contact Info bbfu - Cory Johns join('@', 'darkness', 'yossman.net')
Description:

This module allows you to easily and uniformly set chained hooks for program-level events (such as BEGIN, END, and any signals, including __WARN__ and __DIE__). Multiple hooks can be easily set via a simple, straight-forward, and uniform syntax and they will be called in the order they were defined.

An example usage (from the POD):

use Hooks::Simple( BEGIN => sub { print "Starting...\n" }, END => sub { print "Ending...\n" }, WARN => sub { print "Warning: ", shift }, DIE => sub { print "I'm hit!\n" }, DIE => sub { print "To the death!\n" }, );
package Hooks::Simple; 

=head1 NAME

Hooks::Simple - A simple, uniform way to set chained hooks.

=head1 SYNOPSIS

  use Hooks::Simple(
    BEGIN => sub { print "Starting...\n"    },
    END   => sub { print "Ending...\n"      },
    WARN  => sub { print "Warning: ", shift },
    DIE   => sub { print "I'm hit!\n"       },
    DIE   => sub { print "To the death!\n"  },
  );

=head1 DESCRIPTION

This module provides a simple, uniform way to set chained
hooks in a Perl script.  Because they are chained, you
can define multiple handlers for a single hook and they
will be called in the order that they are defined.

Previously defined handlers, whether they were set via
this module or not, will be preserved and called I<before>
the hooks set via this module (except for END hooks, see
below).

Currently supported hooks are:

=over 4

=item BEGIN

Executed during compilation, immediately after
the handlers for all the other hooks are set up,
before returning from the C<use>.

So, if you C<die()> inside your BEGIN handler, any
handlers you set for DIE will be called.

=item END

Executed just before the script quits (even after
any DIE handlers are called).

Note that, unlike when you define a END subroutine,
these handlers are called I<in the order you define
them>.  Normal END subroutines are called in reverse
order and these handlers are called as if they were
all part of a single END subroutine that occurs at
the C<use> line.

For example, the following code:

  END { print "End 1\n" }
  use Hooks::Simple(
    END => sub { print "End 2\n" },
    END => sub { print "End 3\n" },
  );
  END { print "End 4\n" }

Would print:

  End 4
  End 2
  End 3
  End 1

=item WARN

Executed whenever a warning is generated.

The text of the warning is passed as the first parameter
to I<every> handler and is I<not> printed after all the
handlers have been called.

=item DIE

Executed whenever the program C<die()>'s.

The text of the error is passed as the first parameter
to I<every> handler and is printed to STDERR after all
the handlers have been called.

=item SIGNALS

Any hook other than the ones listed previously are
considered signals and are passed unchecked to %SIG.
If you specify a signal that is not supported on your
system, it will cause a warning.

When a signal is triggered, all the handlers for that signal
are called, in order.  It would be possible to set it up
so that only a single handler is called when a signal is
triggered and then removed from the queue so that the
next time the signal is triggered, the next handler is
called.  But that would be inconsistant with how the
other hooks are handled so I will leave that for a
different module.

Note that SIGALRM is somewhat complicated since you also
need to setup an alarm to trigger the signal.  When the
alarm goes off, all the handlers are called.  If you want
"logical" behaviour, check out Alarm::Queued and/or
Alarm::Concurrent.  Those modules also have the (somewhat
dubious) ability to prevent other code from modifying
%SIG.  This feature may be implemented for this module
in the future.

=back

=head1 CAVEATS

Any hooks that rely on %SIG (including WARN and DIE)
are subsceptible to problems if you modify %SIG.
If you modify, or make any calls to code that modifies,
the elements of %SIG corresponding to handlers you have
installed and don't take care to localize those changes,
your handlers will be lost.

=cut

use strict;
use Carp;

use vars qw/ %HOOKS /;
%HOOKS = ();

sub import {
  my $pkg = shift;

  croak("Odd number of arguments to Hooks::Simple") if(@_ % 2);

  while(@_) {
    my ($key, $code) = (shift, shift);

    if( UNIVERSAL::isa($code, 'CODE') ) {
      push @{$HOOKS{$key}}, $code;
    } else {
      croak("Handler not a CODE reference");
    }
  }

  # Save old WARN and DIE handlers, just in case.
  unshift(@{$HOOKS{DIE}},  $SIG{__DIE__})  if $SIG{__DIE__};
  unshift(@{$HOOKS{WARN}}, $SIG{__WARN__}) if $SIG{__WARN__};

  # Install our custom WARN and DIE handlers.
  $SIG{__DIE__}  = sub { $_->(@_) for(@{$HOOKS{DIE}}) };
  $SIG{__WARN__} = sub { $_->(@_) for(@{$HOOKS{WARN}}) };

  # Install misc. signal handlers.
  my @sigkeys = grep !/^(?:BEGIN|END|WARN|DIE)$/, keys %HOOKS;
  for my $sig (@sigkeys) {
    # Save old handler, if present.
    unshift(@{$HOOKS{$sig}}, $SIG{$sig}) if $SIG{$sig};
    # Install our handler routine.
    $SIG{$sig} = sub { $_->(@_) for(@{$HOOKS{$sig}}) };
  }

  $_->() for(@{$HOOKS{BEGIN}}); # Call all the begin hooks
}

sub END {
  $_->(@_) for(@{$HOOKS{END}}); # Call all the end hooks
}

1;