File Coverage

blib/lib/Exception/Stringy.pm
Criterion Covered Total %
statement 101 107 94.3
branch 40 46 86.9
condition 2 3 66.6
subroutine 24 27 88.8
pod 6 8 75.0
total 173 191 90.5


line stmt bran cond sub pod time code
1             #
2             # This file is part of Exception-Stringy
3             #
4             # This software is Copyright (c) 2014 by Damien Krotkine.
5             #
6             # This is free software, licensed under:
7             #
8             # The Artistic License 2.0 (GPL Compatible)
9             #
10             # ABSTRACT: a Perl Exceptions module where exceptions are not objects but simple strings.
11              
12             package Exception::Stringy;
13             {
14             $Exception::Stringy::VERSION = '0.20';
15             }
16 9     9   162101 use strict;
  5         13  
  5         178  
17 5     5   26 use warnings;
  5         10  
  5         138  
18 5     5   65 use 5.8.9;
  5         17  
  5         470  
19              
20 5     5   42 use Scalar::Util qw(blessed);
  5         72  
  5         745  
21 5     5   27 use Carp;
  5         11  
  5         3007  
22              
23              
24             our ( $_symbol_throw, $_symbol_rethrow, $_symbol_raise, $_symbol_class,
25             $_symbol_isa, $_symbol_fields, $_symbol_field, $_symbol_message,
26             $_symbol_error);
27              
28             my @symbols = qw( throw rethrow raise class isa fields field message error );
29              
30             # regexp to extract header's type and flags
31             my $only_header_r = qr/(\[[^]|]+\|[^]]*\|\])/;
32             my $header_r = qr/\[([^]|]+)(\|([^]]*)\|)\]/;
33             my $klass_r = qr/^([_a-zA-Z:][_a-zA-Z0-9:]*)$/;
34             my $field_name_r = qr/^([_a-zA-Z][_a-zA-Z0-9]*)$/;
35             my $field_value_r = qr/^([^\034:|]*)$/;
36             my $field_value_b64_r = qr|^\034([A-Za-z0-9+/=]+)$|;
37             my $is_b64 = qr|^(\034[A-Za-z0-9+/=]*)$|;
38              
39 5     5   30 no strict 'refs';
  5         10  
  5         1077  
40 5     5   26 no warnings qw(once);
  5         11  
  5         249  
41              
42             my %registered;
43             my %throw_aliases;
44             my %name_aliases;
45              
46 5     5   4991 use MIME::Base64;
  5         4363  
  5         12677  
47              
48             sub _encode {
49 42 100   42   106 defined $_[0]
50             or return '';
51 41 100       396 $_[0] =~ $field_value_r
52             and return $_[0];
53 12         110 "\034" . encode_base64($_[0], '');
54             }
55              
56             sub _decode {
57 22 100   22   184 my ($t) = $_[0] =~ $field_value_b64_r
58             or return $_[0];
59 12         90 decode_base64($t);
60             }
61              
62 139 100   139 0 14806 sub dor ($$) { defined $_[0] ? $_[0] : $_[1] }
63              
64 75     75 0 970 sub Fields { return (); }
65              
66 70     70   4417 sub _fields_hashref { +{ map { $_ => 1 } $_[0]->Fields() } }
  136         601  
67              
68             sub import {
69 8     8   1757 my ($class, %options) = @_;
70 8         17 my $caller = caller;
71              
72 8         55 my $method_prefix = dor($options{method_prefix}, 'x');
73              
74 8         28 foreach (@symbols) {
75 72 100       78 defined ${"${caller}::${method_prefix}$_"}
  72         381  
76             and next;
77 63 50       69 defined ${"${class}::_symbol_$_"}
  63         192  
78             or next;
79 63         64 *{"${caller}::${method_prefix}$_"} = \${"${class}::_symbol_$_"}
  63         274  
  63         134  
80             }
81              
82 8         29 foreach my $k (keys %throw_aliases) {
83 2         4 my $v = $throw_aliases{$k};
84 1         5 $caller->can($k)
85 2 100   1   22 or *{"${caller}::$k"} = sub { $v->throw(@_) };
  1         9  
86             }
87              
88 8         7185 foreach my $k (keys %name_aliases) {
89 0         0 my $v = $name_aliases{$k};
90 0         0 $caller->can($k)
91 0 0   0   0 or *{"${caller}::$k"} = sub () { $v };
  0         0  
92             }
93             }
94              
95             sub declare_exceptions {
96 24     24 1 10037 my $class = shift;
97 24         53 my $caller = caller;
98              
99 24         94 while ( scalar @_ ) {
100 28         56 my $klass = shift;
101 28 100       77 dor($klass, '') =~ $klass_r or _croak(class => $klass);
102 20         40 my $isa = $class;
103 20         25 my ($override, @fields);
104 20 100       77 if (my $r = ref $_[0] ) {
105 16 50       69 $r eq 'HASH' or _croak('exception definition structure' => $r,
106             'It should be HASH');
107 16         21 my %h = %{shift()};
  16         74  
108 16         39 $override = $h{override};
109 16 100       37 @fields =
110 10         30 map { dor($_, '') =~ $field_name_r or _croak(field => $_); $_ }
  16         51  
111 16         22 @{ dor($h{fields}, []) };
112 10 100       41 $h{isa} and $isa = $h{isa};
113              
114 10 100       31 if (length(dor( my $throw_alias = $h{throw_alias}, ''))) {
115 5 100       20 defined $throw_aliases{$throw_alias}
116             and _croak(throw_alias => $throw_alias, 'It has already been defined');
117 4         12 $throw_aliases{$throw_alias} = $klass;
118             }
119              
120 9 100       26 if (length(dor( my $name_alias = $h{name_alias}, ''))) {
121 1 50       4 defined $name_aliases{$name_alias}
122             and _croak(name_alias => $name_alias, 'It has already been defined');
123 1         4 $name_aliases{$name_alias} = $klass;
124             }
125             }
126              
127 13 100 66     100 ! $override && $registered{$klass}
128             and _croak(class => $klass, 'It has already been registered');
129              
130 11         21 unshift @{"${klass}::ISA"}, $isa;
  11         190  
131 11         18 @{"${klass}::_internal_fields"} = @fields;
  11         60  
132 11     42   747 eval "package $klass; sub Fields { (\$_[0]->SUPER::Fields, \@${klass}::_internal_fields) }";
  42     36   288  
  36         355  
133 11         60 $registered{$klass} = 1;
134             }
135              
136 7         26 foreach my $k (keys %throw_aliases) {
137 6         27 my $v = $throw_aliases{$k};
138 4         50 $caller->can($k)
139 6 100   3   89 or *{"${caller}::$k"} = sub { $v->throw(@_) };
  3         601  
140             }
141              
142 7         33 foreach my $k (keys %name_aliases) {
143 2         4 my $v = $name_aliases{$k};
144 1         4022 $caller->can($k)
145 2 100   0   32 or *{"${caller}::$k"} = sub () { $v };
  0         0  
146             }
147              
148             }
149              
150 17 100   17   57 sub _croak { croak $_[0] . " '" . dor($_[1], '') . "' is invalid" . ($_[2] ? ". $_[2]" : '') }
151              
152             # Class methods
153              
154             sub new {
155 37     37 1 14091 my ($class, $message, %fields) = @_;
156 37 50       138 $registered{$class} or croak "exception class '$class' has not been registered yet";
157 24         79 '[' . $class . '|' . join('|',
158 26 100       97 map { $_ . ':' . _encode($fields{$_}) }
159 37         190 grep { $class->_fields_hashref()->{$_}
160             or croak "invalid field '$_', exception class '$class' didn't declare it"
161             }
162             keys %fields
163             ) . '|]' . dor($message, '');
164             }
165              
166 0     0 1 0 sub raise { croak shift->new(@_)}
167 7     7 1 1574 sub throw { croak shift->new(@_)}
168              
169             sub registered_fields {
170 2     2 1 6 my ($class) = @_;
171 2         77 $class->Fields();
172             }
173              
174 3     3 1 64 sub registered_exception_classes { keys %registered }
175              
176             # fake methods (class methods with exception as first argument)
177              
178             $_symbol_throw = sub { croak $_[0] };
179             $_symbol_rethrow = sub { die $_[0] };
180             $_symbol_raise = sub { croak $_[0] };
181              
182             $_symbol_class = sub {
183             my ($class) = $_[0] =~ $header_r
184             or _croak(exception => $_[0]);
185             $class;
186             };
187              
188             $_symbol_isa = sub {
189             my $class = blessed($_[0]);
190             if ( ! defined $class ) {
191             ($class) = $_[0] =~ $header_r
192             or return;
193             }
194             $class->isa($_[1]);
195             };
196              
197             $_symbol_fields = sub {
198             my ($class, $fields) = $_[0] =~ $header_r
199             or _croak(exception => $_[0]);
200             map { (split(/:/, $_))[0] } split(/\|/, $fields);
201             };
202              
203             $_symbol_field = sub {
204             my $f = $_[1];
205             my ($class, $fields) = $_[0] =~ $header_r
206             or _croak(exception => $_[0]);
207             my $regexp = qr/\|$f:(.*?)\|/;
208             $class->_fields_hashref()->{$f}
209             or _croak(field => $f, "It is unknown for this exception class ('$class')");
210             if (@_ < 3) {
211             defined (my $value = ($fields =~ $regexp)[0])
212             or return;
213             return _decode( $value );
214             }
215              
216             $fields =~ s/$regexp/|/;
217             $fields =~ s/^\|\|$/|/;
218             my $v = _encode($_[2]);
219             my $was_ro = Internals::SvREADONLY($_[0]);
220             Internals::SvREADONLY($_[0] => 0);
221             $_[0] =~ s/$header_r/[$class$fields$f:$v|]/;
222             Internals::SvREADONLY($_[0] => $was_ro);
223             return;
224             };
225              
226             $_symbol_message = sub {
227             if (@_ < 2) {
228             my $message = $_[0];
229             $message =~ s/$only_header_r//
230             or _croak(exception => $_[0]);
231             return $message;
232             }
233              
234             my ($header) = $_[0] =~ $only_header_r
235             or _croak(exception => $_[0]);
236             my $was_ro = Internals::SvREADONLY($_[0]);
237             Internals::SvREADONLY($_[0] => 0);
238             $_[0] = "$header$_[1]";
239             Internals::SvREADONLY($_[0] => $was_ro);
240             return $_[0];
241             };
242              
243             $_symbol_error = $_symbol_message;
244              
245             1;
246              
247             __END__