File Coverage

blib/lib/Try/Tiny/SmartCatch.pm
Criterion Covered Total %
statement 17 96 17.7
branch 0 52 0.0
condition 0 15 0.0
subroutine 6 17 35.2
pod 6 6 100.0
total 29 186 15.5


line stmt bran cond sub pod time code
1             package Try::Tiny::SmartCatch;
2              
3 1     1   24765 use 5.006;
  1         3  
  1         40  
4 1     1   6 use strict;
  1         2  
  1         34  
5 1     1   5 use warnings;
  1         15  
  1         36  
6              
7 1     1   5 use vars qw/@EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION @ISA/;
  1         2  
  1         127  
8              
9             BEGIN {
10 1     1   6 require Exporter;
11 1         1034 @ISA = qw/Exporter/;
12             }
13              
14             @EXPORT = qw/try catch_when catch_default then finally/;
15             @EXPORT_OK = (@EXPORT, qw/throw/);
16             %EXPORT_TAGS = (
17             all => [@EXPORT_OK],
18             );
19              
20             ++$Carp::Internal{+__PACKAGE__};
21              
22             $VERSION = '0.5';
23              
24             sub try($;@) {
25 0     0 1   my ($try, @code_refs) = @_;
26              
27 0           my ($catch_default, @catch_when, $code_ref, @finally, $ref_type, $then, $wantarray);
28              
29 0           $wantarray = wantarray ();
30              
31 0           foreach $code_ref (@code_refs) {
32 0 0         next if (!$code_ref);
33              
34 0           $ref_type = ref($code_ref);
35              
36             ## zero or more 'catch_when' blocks
37 0 0         if ($ref_type eq 'Try::Tiny::SmartCatch::Catch::When') {
    0          
    0          
    0          
38             ## we need to save same handler for many different exception types
39 0           push(@catch_when, $code_ref);
40             }
41             ## zero or one 'catch_default' blocks
42             elsif ($ref_type eq 'Try::Tiny::SmartCatch::Catch::Default') {
43 0 0         $catch_default = $$code_ref{code}
44             if (!defined($catch_default));
45             }
46             ## zero or more 'finally' blocks
47             elsif ($ref_type eq 'Try::Tiny::SmartCatch::Finally') {
48 0           push(@finally, $$code_ref);
49             }
50             ## zero or one 'then' blocks
51             elsif ($ref_type eq 'Try::Tiny::SmartCatch::Then') {
52 0 0         $then = $$code_ref
53             if (!defined($then));
54             }
55             ## unknown block type
56             else {
57 0           require Carp;
58 0           Carp::confess("Unknown code ref type given '$ref_type'. Check your usage & try again");
59             }
60             }
61              
62 0           my ($error, $failed, $prev_error, @ret);
63              
64             ## save the value of $@ so we can set $@ back to it in the beginning of the eval
65 0           $prev_error = $@;
66              
67             {
68             ## localize $@ to prevent clobbering of previous value by a successful eval.
69 0           local $@;
  0            
70              
71             ## failed will be true if the eval dies, because 1 will not be returned from the eval body
72 0           $failed = not eval {
73 0           $@ = $prev_error;
74              
75             ## call try block in list context if try subroutine is called in list context, or we have 'then' block
76             ## result of 'try' block is passed as arguments to then block, so we need do that in that way
77 0 0 0       if ($wantarray || $then) {
    0          
78 0           @ret = &$try();
79             }
80             elsif (defined($wantarray)) {
81 0           $ret[0] = &$try();
82             }
83             else {
84 0           &$try();
85             }
86              
87             ## properly set $fail to false
88 0           return 1;
89             };
90              
91             ## copy $@ to $error; when we leave this scope, local $@ will revert $@
92             ## back to its previous value
93 0           $error = $@;
94             }
95              
96             ## set up a scope guard to invoke the finally block at the end
97 0 0         my @guards = (
98             map {
99 0           Try::Tiny::SmartCatch::ScopeGuard->_new($_, $failed ? $error : ())
100             } @finally
101             );
102              
103             ## at this point $failed contains a true value if the eval died, even if some
104             ## destructor overwrote $@ as the eval was unwinding.
105 0 0         if ($failed) {
106             ## if we got an error, invoke the catch block.
107 0 0 0       if (scalar(@catch_when) || $catch_default) {
108              
109             ## This works like given($error), but is backwards compatible and
110             ## sets $_ in the dynamic scope for the body of $catch
111 0           for ($error) {
112 0           my ($catch_data);
113 0           foreach $catch_data (@catch_when) {
114 0 0         return &{$$catch_data{code}}($error)
  0            
115             if ($catch_data->for_error($error));
116             }
117              
118 0 0         return &$catch_default($error)
119             if ($catch_default);
120              
121 0           die($error);
122             }
123             }
124              
125 0           return;
126             }
127              
128             ## no failure, $@ is back to what it was, everything is fine
129             else {
130             ## do we have then block? if we does, execute it in correct context
131 0 0         if ($then) {
132 0 0         if ($wantarray) {
    0          
133 0           @ret = &$then(@ret);
134             }
135             elsif (defined($wantarray)) {
136 0           $ret[0] = &$then(@ret);
137             }
138             else {
139 0           &$then(@ret);
140             }
141             }
142              
143 0 0         return if (!defined($wantarray));
144 0 0         return $wantarray ? @ret : $ret[0];
145             }
146             }
147              
148             sub catch_when ($$;@) {
149 0     0 1   my ($types, $block) = (shift(@_), shift(@_));
150              
151 0           my $catch = Try::Tiny::SmartCatch::Catch::When->new($block, $types);
152              
153 0           return ($catch, @_);
154             }
155              
156             sub catch_default ($;@) {
157 0     0 1   my $block = shift(@_);
158              
159 0           my $catch = Try::Tiny::SmartCatch::Catch::Default->new($block);
160              
161 0           return ($catch, @_);
162             }
163              
164             sub then ($;@) {
165 0     0 1   my $block = shift(@_);
166              
167 0           my $then = bless(\$block, 'Try::Tiny::SmartCatch::Then');
168              
169 0           return ($then, @_);
170             }
171              
172             sub finally ($;@) {
173 0     0 1   my $block = shift(@_);
174              
175 0           my $finally = bless(\$block, 'Try::Tiny::SmartCatch::Finally');
176              
177 0           return ($finally, @_);
178             }
179              
180             sub throw {
181 0     0 1   return die (@_);
182             }
183              
184             package # hide from PAUSE
185             Try::Tiny::SmartCatch::ScopeGuard;
186             {
187              
188             sub _new {
189 0     0     shift(@_);
190 0           return bless([ @_ ]);
191             }
192              
193             sub DESTROY {
194 0     0     my ($guts) = @_;
195              
196 0           my $code = shift(@$guts);
197 0           return &$code(@$guts);
198             }
199             }
200              
201             package Try::Tiny::SmartCatch::Catch::Default;
202             {
203             sub new {
204 0     0     my ($class, $code) = @_;
205              
206 0           my $self = { code => $code };
207 0           $self = bless($self, $class);
208              
209 0           return $self;
210             }
211             }
212              
213             package Try::Tiny::SmartCatch::Catch::When;
214             {
215 1     1   43 use Scalar::Util qw/blessed/;
  1         2  
  1         379  
216              
217             sub new {
218 0     0     my ($class, $code, $types) = @_;
219              
220 0 0         my $self = {
    0          
221             code => $code,
222             types => (
223             ref($types) eq 'ARRAY' ? $types :
224             defined($types) ? [$types] :
225             []
226             ),
227             };
228              
229 0           return bless($self, $class);
230             }
231              
232             sub for_error {
233 0     0     my ($self, $error, $types) = @_;
234              
235 0 0         $types = $$self{types}
236             if (!defined($types));
237 0 0         $types = [$types]
238             if (ref($types) ne 'ARRAY');
239              
240 0 0         if (blessed($error)) {
241 0           foreach (@$types) {
242 0 0         return 1 if ($error->isa($_));
243             }
244             }
245             else {
246 0           my $type;
247 0           foreach $type (@$types) {
248 0 0 0       return 1 if (
      0        
      0        
249             (ref($type) eq 'Regexp' && $error =~ /$type/) ||
250             (!ref($type) && index($error, $type) > -1)
251             );
252             }
253             }
254              
255 0           return;
256             }
257              
258             }
259              
260              
261             1;
262              
263             __END__