File Coverage

blib/lib/X/Tiny/Base.pm
Criterion Covered Total %
statement 57 60 95.0
branch 8 12 66.6
condition 6 12 50.0
subroutine 13 14 92.8
pod 2 4 50.0
total 86 102 84.3


line stmt bran cond sub pod time code
1             package X::Tiny::Base;
2              
3 2     2   1970 use strict;
  2         2  
  2         101  
4 2     2   9 use warnings;
  2         3  
  2         106  
5              
6             my %CALL_STACK;
7              
8             my %PROPAGATIONS;
9              
10             =encoding utf-8
11              
12             =head1 NAME
13              
14             X::Tiny::Base - super-light exception base class
15              
16             =head1 SYNOPSIS
17              
18             package My::Module::X::Base;
19              
20             use parent qw( X::Tiny::Base );
21              
22             sub _new {
23             my ($class, @args) = @_;
24              
25             ...
26             }
27              
28             sub get {
29             my ($self, $attr_name) = @_;
30              
31             ...
32             }
33              
34             sub to_string { ... }
35              
36             #If you override this, be sure also to call the base method.
37             sub DESTROY {
38             my ($self) = @_;
39              
40             ...
41              
42             #vv This. Be sure to do this in your override method.
43             $self->SUPER::DESTROY();
44             }
45              
46             =head1 DESCRIPTION
47              
48             This base class is meant for you to subclass into your distribution’s own
49             exception base class (e.g., C); you should then
50             subclass that base class for your distribution’s specific exception classes
51             (e.g., C).
52              
53             C, then, serves two functions:
54              
55             =over
56              
57             =item 1) It is a useful set of defaults for overridable methods.
58              
59             =item 2) Framework handling of L stringification behavior,
60             e.g., when an uncaught exception is printed.
61              
62             =back
63              
64             That stringification’s precise formatting is not defined; however, it
65             will always include:
66              
67             =over
68              
69             =item * A stack trace
70              
71             =item * Propagations
72              
73             =back
74              
75             There is currently no access provided in code to these; if that’s something
76             you’d like to have, let me know.
77              
78             =head1 SUBCLASS INTERFACE
79              
80             The default behaviors seem pretty usable and desirable to me, but there may
81             be circumstances where someone wants other behaviors. Toward that end,
82             the following methods are meant to be overridden in subclasses:
83              
84             =head2 I->OVERLOAD()
85              
86             Returns a boolean to indicate whether this exception class should load
87             L as part of creating exceptions. If you don’t want the
88             memory overhead of L, then make this return 0. It returns 1
89             by default.
90              
91             You might also make this 0 if, for example, you want to handle the
92             L behavior yourself. (But at that point, why use X::Tiny??)
93              
94             =cut
95              
96 2     2   9 use constant OVERLOAD => 1;
  2         3  
  2         1734  
97              
98             =head2 I->_new( MESSAGE, KEY1 => VALUE1, .. )
99              
100             The main constructor. Whatever args this accepts are the args that
101             you should use to create exceptions via your L subclass’s
102             C method. You’re free to design whatever internal representation
103             you want for your class: hash reference, array reference, etc.
104              
105             The default implementation accepts a string message and, optionally, a
106             list of key/value pairs. It is useful that subclasses of your base class
107             define their own MESSAGE, so all you’ll pass in is a specific piece of
108             information about this instance—e.g., an error code, a parameter name, etc.
109              
110             =cut
111              
112             sub _new {
113 5     5   18 my ( $class, $string, %attrs ) = @_;
114              
115 5         14 return bless [ $string, \%attrs ], $class;
116             }
117              
118             =head2 I->get( ATTRIBUTE_NAME )
119              
120             Retrieves the value of an attribute.
121              
122             =cut
123              
124             sub get {
125 0     0 1 0 my ( $self, $attr ) = @_;
126              
127             #Do we need to clone this? Could JSON suffice, or do we need Clone?
128 0         0 return $self->[1]{$attr};
129             }
130              
131             =head2 I->to_string()
132              
133             Creates a simple string representation of your exception. The default
134             implementation contains the class and the MESSAGE given on instantiation.
135              
136             This method’s return value should B include a strack trace;
137             L’s internals handle that one for you.
138              
139             =cut
140              
141             sub to_string {
142 6     6 1 5 my ($self) = @_;
143              
144 6         28 return sprintf '%s: %s', ref($self), $self->[0];
145             }
146              
147             #----------------------------------------------------------------------
148              
149             =head1 DESTRUCTOR METHODS
150              
151             If you define your own C method, make sure you also call
152             C, or else you’ll get memory leaks as L’s
153             internal tracking of object properties will never be cleared out.
154              
155             =cut
156              
157             sub DESTROY {
158 5     5   1780 my ($self) = @_;
159              
160 5         12 delete $CALL_STACK{$self->_get_strval()};
161 5         33 delete $PROPAGATIONS{$self->_get_strval()};
162              
163 5         63 return;
164             }
165              
166             #----------------------------------------------------------------------
167              
168             sub new {
169 5     5 0 8 my ($class, @args) = @_;
170              
171 5 50       36 $class->_check_overload() if $class->OVERLOAD();
172              
173 5         12 my $self = $class->_new(@args);
174              
175 5         11 $CALL_STACK{$self->_get_strval()} = [ _get_call_stack(2) ];
176              
177 5         69 return $self;
178             }
179              
180             #----------------------------------------------------------------------
181              
182             sub PROPAGATE {
183 1     1 0 2 my ($self, $file, $line) = @_;
184              
185 1         1 push @{ $PROPAGATIONS{$self->_get_strval()} }, [ $file, $line ];
  1         2  
186              
187 1         7 return $self;
188             }
189              
190             my %_OVERLOADED;
191              
192             sub _check_overload {
193 5     5   5 my ( $class, $str ) = @_;
194              
195             #cf. eval_bug.readme
196 5         6 my $eval_err = $@;
197              
198 2   66 2   9 $_OVERLOADED{$class} ||= eval qq{
  2         3  
  2         25  
  5         175  
199             package $class;
200             use overload (q<""> => __PACKAGE__->can('__spew'));
201             1;
202             };
203              
204             #Should never happen as long as overload.pm is available.
205 5 50       12 warn if !$_OVERLOADED{$class};
206              
207 5         4 $@ = $eval_err;
208              
209 5         8 return;
210             }
211              
212             sub _get_strval {
213 29     29   27 my ($self) = @_;
214              
215 29 50 33     163 if ( overload->can('Overloaded') && overload::Overloaded($self) ) {
216 29         3686 return overload::StrVal($self);
217             }
218              
219 0         0 return q<> . $self;
220             }
221              
222             sub _get_call_stack {
223 5     5   6 my ($level) = @_;
224              
225 5         4 my @stack;
226              
227 5         51 while ( my @call = (caller $level)[3, 1, 2] ) {
228 12         44 my ($pkg) = ($call[0] =~ m<(.+)::>);
229              
230 12 100 33     99 if (!$pkg || (!$pkg->isa(__PACKAGE__) && !$pkg->isa('X::Tiny'))) {
      66        
231 7         10 push @stack, \@call;
232             }
233              
234 12         59 $level++;
235             }
236              
237 5         27 return @stack;
238             }
239              
240             sub __spew {
241 6     6   593 my ($self) = @_;
242              
243 6         14 my $spew = $self->to_string();
244              
245 6 50       24 if ( rindex($spew, $/) != (length($spew) - length($/)) ) {
246 6         6 $spew .= $/ . join( q<>, map { "\tfrom $_->[0] ($_->[1], line $_->[2])$/" } @{ $CALL_STACK{$self->_get_strval()} } );
  10         70  
  6         10  
247             }
248              
249 6 100       16 if ( $PROPAGATIONS{ $self->_get_strval() } ) {
250 1         4 $spew .= join( q<>, map { "\t...propagated at $_->[0], line $_->[1]$/" } @{ $PROPAGATIONS{$self->_get_strval()} } );
  1         9  
  1         2  
251             }
252              
253 6         75 return $spew;
254             }
255              
256             1;