Filter::Cleanup - A stackable way to deal with error handling
Version 0.01
use Filter::Cleanup; use Filter::Cleanup debug => 1; sub foo { my $file_path = shift; open my $FH, $file_path or die $!; cleanup { close $FH }; do_risky_stuff_with_fh($FH); } sub html { # cleanups stack and execute in reverse order cleanup { print "</html>" }; cleanup { print "</body>" }; print "<html>\n"; print "<head><title>Test page</title></head>\n"; print "<body>\n"; print generate_page_body(); }
Filter::Cleanup provides a simple way to deal with cleaning up after multiple error conditions modeled after the D programming language's scope(exit) mechanism.
scope(exit)
Each cleanup block operates in essentially the same manner as a finally block in languages supporting try/catch/finally style error handling.
cleanup
finally
cleanup blocks may be placed anywhere in a scope. All statements lexically scoped after the cleanup block will be wrapped in an eval. Should an error be triggered within the block, the cleanup statement will be called before any error is rethrown (using croak).
eval
croak
Within the cleanup block, the status of $@ may be inspected normally.
$@
Multiple cleanup blocks stack, and each MUST be followed by a semi-colon to ensure proper organization of the outputted code. cleanups are executed in reverse order (it's a stack, see?) and may be nested, although this defeats the purpose. The reason for reverse execution is that each cleanup represents another nested level of evals and clean-up code.
Take the following code:
use Filter::Cleanup; sub example { cleanup { print "FOO" }; print "BAR"; return 1; }
This is roughly the output of the source filter:
sub example { my $result = eval { print "BAR"; return 1; }; my $error = $@; print "FOO"; if ($error) { croak $error; } else { $result; # returns 1 } }
Now with multiple cleanups:
use Filter::Cleanup; sub example { cleanup { print "FOO" }; cleanup { print "BAZ" }; print "BAR"; return 1; }
The following code would be generated:
sub example { my $result = eval { my $result = eval { print "BAR"; return 1; }; my $error = $@; print "BAZ"; if ($error) { croak $error; } else { $result; } }; my $error = $@; print "FOO"; if ($error) { croak $error; } else { $result; # returns 1 } }
Internally, PPI is used to parse the module and generate the new code. This is because there are so many different forms which could proceed a cleanup block that there is no more efficient way to ensure that valid code is emitted. PPI has proven to be stable, robust, and very reasonably efficient.
This can sometimes have surprising results due to the manner in which cleanup blocks are evaluated. By the time the cleanup block executes, the result of evaluating the protected code has already been determined and stored. Cleanup blocks are then processed, and their results are discarded after being inspected for errors. Therefore, something like this:
Cleanup
sub test { my @words = ('foo'); cleanup { push @words, 'bat' }; cleanup { push @words, 'baz' }; cleanup { push @words, 'bar' }; return @words; }
...will cause 'foo' to be returned, because @words has not been modified by the time the return value is calculated.
In order to effect changes in return values in cleanup (a questionable practice, but hey, I don't judge), a reference is required:
sub test { my $words = ['foo']; cleanup { push @$words, 'bat' }; cleanup { push @$words, 'baz' }; cleanup { push @$words, 'bar' }; return $words; }
The above code will return ['foo', 'bar', 'baz', 'bat'].
['foo', 'bar', 'baz', 'bat']
Importing Filter::Cleanup makes the cleanup keyword available in the importing scope. Adding debug=1> will cause the generated code to be printed to STDERR with line numbers.
debug=
STDERR
Expected by Filter::Util::Call; provides the entry point into the source filter.
Performs the actual work of modifying the source.
Jeff Ober mailto:jeffober@gmail.com
BSD license
To install Filter::Cleanup, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Filter::Cleanup
CPAN shell
perl -MCPAN -e shell install Filter::Cleanup
For more information on module installation, please visit the detailed CPAN module installation guide.