File Coverage

blib/lib/ConditionSystem.pm
Criterion Covered Total %
statement 24 51 47.0
branch 0 4 0.0
condition 0 3 0.0
subroutine 9 18 50.0
pod 6 6 100.0
total 39 82 47.5


line stmt bran cond sub pod time code
1             package ConditionSystem;
2             BEGIN {
3 1     1   24879 $ConditionSystem::VERSION = '0.02';
4             }
5             # ABSTRACT: A Common Lisp like condition/restart system for exceptions
6              
7 1     1   8 use strict;
  1         1  
  1         31  
8 1     1   4 use warnings FATAL => 'all';
  1         2  
  1         35  
9              
10 1     1   1649 use Scope::Upper qw( unwind :words );
  1         1287  
  1         183  
11 1     1   6 use Scalar::Util 'blessed';
  1         2  
  1         110  
12 1     1   16426 use Try::Tiny;
  1         15726  
  1         94  
13              
14 1         16 use Sub::Exporter -setup => {
15             exports => [qw( restart with_handlers bind_continue handle restart_case )],
16             groups => {
17             default => [qw( restart with_handlers bind_continue handle restart_case )]
18             }
19 1     1   4863 };
  1         16910  
20              
21              
22             our %handlers;
23             our %cases;
24              
25             BEGIN {
26 1     1   529 no strict 'refs';
  1         2  
  1         157  
27 1         364 *{'CORE::GLOBAL::die'} = sub {
28 0     0     my $err = shift;
29 0           for my $handles (keys %handlers) {
30 0 0         if($err->isa($handles)) {
31 0           my $handler = $handlers{$handles};
32 0 0 0       $handler = ${$handler}
  0            
33             if blessed($handler) && $handler->isa('Try::Tiny::Catch');
34 0           unwind $handler->($err) => UP UP HERE;
35 0           return "Well, it should never get here...";
36             }
37             }
38 1     1   4 };
39             };
40              
41              
42             sub with_handlers (&@) {
43 0     0 1   my ($code, %handles) = @_;
44 0           %handlers = %handles; # XXX Should push onto each handler as a queue
45 0           my @ret = $code->();
46 0           %handlers = ();
47 0           return @ret;
48             }
49              
50              
51             sub continue_with (&) {
52 0     0 1   my @vals = @_;
53 0     0     return sub { @vals }
54 0           }
55              
56              
57             sub restart {
58 0     0 1   my $name = shift;
59 0           my @args = @_;
60             return sub {
61 0     0     $cases{$name}->(@args)
62 0           };
63             }
64              
65              
66             sub restart_case (&@) {
67 0     0 1   my $error = shift->();
68 0           %cases = @_;
69 0           die $error;
70             }
71              
72             # Nom. Sugarz
73              
74              
75             sub handle {
76 0     0 1   my ($handles, $code) = @_;
77 0           return $handles => $code;
78             }
79              
80              
81             sub bind_continue {
82 0     0 1   my ($restart, $code) = @_;
83 0           return $restart => $code;
84             }
85              
86             1;
87              
88             __END__
89             =pod
90              
91             =encoding utf-8
92              
93             =head1 NAME
94              
95             ConditionSystem - A Common Lisp like condition/restart system for exceptions
96              
97             =head1 SYNOPSIS
98              
99             {
100             package MalformedLogEntry;
101             use Moose;
102             extends 'Throwable::Error';
103              
104             has bad_data => ( is => 'ro' );
105              
106             package LogParser;
107             use Conditions;
108             sub parse_log_entry {
109             my $entry = shift or die "Must specify entry";
110             if($entry =~ /(\d+-\d+-\d+) (\d+:\d+:\d+) (\w+) (.*)/) {
111             return ($1, $2, $3, $4);
112             }
113             else {
114             restart_case {
115             MalformedLogEntry->new($entry),
116             }
117             bind_continue(use_value => sub { return shift }),
118             bind_continue(log => sub {
119             warn "*** Invalid entry: $entry";
120             return undef;
121             });
122             }
123             };
124              
125             package MyApp;
126             use Conditions;
127             my @logs = with_handlers {
128             [ parse_log_entry('2010-01-01 10:09:5 WARN Test') ],
129             [ parse_log_entry('Oh no bad data') ],
130             [ parse_log_entry('2010-10-12 12:11:03 INFO Notice it still carries on!') ];
131             }
132             handle(MalformedLogEntry => restart('log'));
133              
134             # @logs contains 3 logs, the 2nd of which is 'undef'
135             # A single warning will have been printed to STDERR as well.
136             };
137              
138             =head1 DESCRIPTION
139              
140             This distribution implements a Common Lisp-like approach to exception handling,
141             providing both a mechanism for throwing/catching exceptions, but also a
142             mechanism for continuing on from an exception via a non-local exit. This
143             essentially allows you "fix" the code that was throwing an exception from
144             outside that code itself, rather than trying to handle stuff when it's already
145             too late.
146              
147             For a good introduction to the condition system (that this was all inspired by),
148             I highly recommend L<Practical Common Lisp|http://gigamonkeys.com/book/>, in
149             particular the chapter
150             L<Beyond Exception Handling|http://gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html>
151              
152             B<HALT!> This module is both very new, and does some fairly crazy things, and
153             as such may not be ready for prime time usage. However, the basic test cases
154             do pass, so maybe you will have some luck. I encourage the usage of this module
155             for a bit of fun, and exploration for now. Hopefully it will mature into a
156             production ready module, but it's not there yet. But with your help, it can be
157             so... please submit patches, bug reports and all that goodness.
158              
159             =head1 FUNCTIONS
160              
161             =head2 with_handlers
162              
163             Run a block of code, and if any exception is raised, try and invoke one of the
164             handlers.
165              
166             with_handlers {
167             # Dangerous code...
168             }
169             handle(ExceptionType => sub {
170             # Recovery
171             });
172              
173             =head2 continue_with
174              
175             Return from a restart with a specific value.
176              
177             with_handlers {
178             my $foo = restart_case {
179             Exception->new
180             }
181             # foo is 500
182             }
183             handle(Exception => continue_with { 500 });
184              
185             =head2 restart
186              
187             Invoke a restart with a specific name, and pass extra arguments through.
188              
189             with_handlers {
190             restart_case {
191             Exception->new
192             }
193             bind_restart(Log => sub {
194             warn "An Exception was raised";
195             });
196             } handle(Exception => restart('Log'))
197              
198             =head2 restart_case
199              
200             Throw an exception (from a specified block) with pre-defined strategies on
201             how to resume execution later.
202              
203             restart_case { Exception->new }
204             bind_restart(delegate_responsibility => sub {
205             Boss->email($bug_report)
206             })
207              
208             The body of C<restart_case> must yield an exception, and will be when the
209             restart case is invoked. There may be 0 to many restarts provided. Restarts
210             are invoked by L<restart>, called from a handler set up with L<with_handlers>.
211              
212             =head2 handle
213              
214             Create a handler for a given exception type, and associated code reference:
215              
216             handle('Exception::Class' => sub {
217             # Handle exception here...
218             });
219              
220             =head2 bind_continue
221              
222             Bind a restart for the scope of a restart_case block, with a given name and
223             code reference:
224              
225             bind_continue(panic => sub {
226             warn "OMG OMG OMG OMG";
227             });
228              
229             =head1 AUTHOR
230              
231             Oliver Charles
232              
233             =head1 COPYRIGHT AND LICENSE
234              
235             This software is copyright (c) 2011 by Oliver Charles <oliver.g.charles@googlemail.com>.
236              
237             This is free software; you can redistribute it and/or modify it under
238             the same terms as the Perl 5 programming language system itself.
239              
240             =cut
241