File Coverage

lib/Badger/Base.pm
Criterion Covered Total %
statement 204 217 94.0
branch 76 96 79.1
condition 69 116 59.4
subroutine 32 36 88.8
pod 20 20 100.0
total 401 485 82.6


line stmt bran cond sub pod time code
1             #========================================================================
2             #
3             # Badger::Base
4             #
5             # DESCRIPTION
6             # Base class module implementing common functionality for various
7             # other Badger modules.
8             #
9             # AUTHOR
10             # Andy Wardley
11             #
12             #========================================================================
13              
14             package Badger::Base;
15              
16             use Badger::Class
17 70         578 version => 0.01,
18             debug => 0,
19             constants => 'CODE HASH ARRAY BLANK SPACE PKG REFS ONCE WARN NONE',
20             import => 'class classes',
21             utils => 'blessed reftype xprintf',
22             words => 'ID EXCEPTION THROWS ERROR DECLINED before after',
23             constant => {
24             base_id => 'Badger', # stripped from class name to make id
25             TRIAL => 'Badger::Base::Trial',
26 70     70   3800 };
  70         102  
27              
28 70     70   35205 use Badger::Exception; # TODO: autoload
  70         151  
  70         507  
29 70     70   417 use Badger::Debug 'debug debug_up dump_data_inline dump_data dump_list dump_hash';
  70         107  
  70         215  
30              
31             our $EXCEPTION = 'Badger::Exception' unless defined $EXCEPTION;
32             our $ON_WARN = WARN;
33             our $MESSAGES = {
34             not_found => '%s not found: %s',
35             not_found_in => '%s not found in %s',
36             not_implemented => '%s is not implemented %s',
37             no_component => 'No %s component defined',
38             bad_method => "Invalid method '%s' called on %s at %s line %s",
39             invalid => 'Invalid %s specified: %s',
40             unexpected => 'Invalid %s specified: %s (expected a %s)',
41             missing_to => 'No %s specified to %s',
42             missing => 'No %s specified',
43             todo => '%s is TODO %s',
44             at_line => '%s at line %s',
45             at_file_line => '%s in %s at line %s',
46             };
47              
48              
49             sub new {
50 457     457 1 1338 my $class = shift;
51              
52             # install warning handling for odd number of parameters when DEBUG enabled
53             local $SIG{__WARN__} = sub {
54 0     0   0 Badger::Utils::odd_params(@_);
55 457         495 } if DEBUG;
56              
57 457 100 100     1901 my $args = @_ && ref $_[0] eq HASH ? shift : { @_ };
58 457   33     1564 my $self = bless { }, ref $class || $class;
59 457         1453 $self = $self->init($args);
60              
61             # be careful to account for object that overload the boolean comparison
62             # operator and may return false to a simple truth test.
63 454 50       2070 return defined $self
64             ? $self
65             : $self->error("init() method failed\n");
66             }
67              
68             sub init {
69 60     60 1 90 my $self = shift;
70             # default action is to store reference to entire configuration so
71             # that methods can examine it later if they need to
72 60         217 $self->{ config } = shift;
73 60         104 return $self;
74             }
75              
76             sub warn {
77 8     8 1 45 my $self = shift;
78 8 50       12 return unless @_;
79              
80 8         16 my $message = join(BLANK, @_);
81 8         17 my $handlers = $self->on_warn;
82              
83 8         9 $self->debug("dispatching handlers for warn: ", $self->dump_data_inline($handlers), "\n") if DEBUG;
84 8 50 33     34 $self->_dispatch_handlers( warn => $handlers => $message )
85             if $handlers && @$handlers;
86              
87             # Warning is usually raised by the last handler in the chain which
88             # defaults to 'warn', so it's OK to just drop out here.
89             }
90              
91             sub error {
92 71     71 1 236 my $self = shift;
93 71   66     171 my $class = ref $self || $self;
94 71   100     203 my $type = reftype $self || BLANK;
95 70     70   507 no strict REFS;
  70         130  
  70         2742  
96 70     70   381 no warnings ONCE;
  70         106  
  70         17390  
97              
98 71 100       145 if (@_) {
    100          
99             # don't stringify objects passed as argument
100 58 50       147 my $message = ref $_[0] ? shift : join(BLANK, map { defined($_) ? $_ : BLANK } @_);
  63 50       205  
101 58         211 my $handlers = $self->on_error;
102              
103             # set package variable
104 58         68 ${ $class.PKG.ERROR } = $message;
  58         199  
105 58         83 ${ $class.PKG.DECLINED } = 0;
  58         149  
106              
107 58 100       124 if ($type eq HASH) {
108             # set ERROR and DECLINED items in object
109 52         76 $self->{ ERROR } = $message;
110 52         80 $self->{ DECLINED } = 0;
111             }
112              
113 58 100 66     194 ($message) = $self->_dispatch_handlers( error => $handlers => $message )
114             if $handlers && @$handlers;
115              
116 58         183 $self->throw($message);
117             }
118             elsif ($type eq HASH) {
119 11         42 return $self->{ ERROR };
120             }
121             else {
122 2         12 return ${ $class.PKG.ERROR };
  2         16  
123             }
124             # not reached
125             }
126              
127             sub decline {
128 37     37 1 73 my $self = shift;
129 37   33     82 my $class = ref $self || $self;
130 37   50     94 my $type = reftype $self || BLANK;
131 37 50       76 my $reason = @_ == 1 ? shift : join(BLANK, @_);
132 70     70   458 no strict REFS;
  70         112  
  70         2312  
133 70     70   342 no warnings ONCE;
  70         135  
  70         9218  
134              
135 37         48 ${ $class.PKG.ERROR } = $reason;
  37         108  
136 37         40 ${ $class.PKG.DECLINED } = 1;
  37         85  
137              
138 37 50       74 if ($type eq HASH) {
139 37         56 $self->{ ERROR } = $reason;
140 37         67 $self->{ DECLINED } = 1;
141             }
142              
143 37         90 return undef;
144             }
145              
146             sub declined {
147 2     2 1 3 my $self = shift;
148 2   33     5 my $class = ref $self || $self;
149 2   50     7 my $type = reftype $self || BLANK;
150 70     70   449 no strict REFS;
  70         153  
  70         6762  
151             return ($type eq HASH)
152             ? $self->{ DECLINED }
153 2 50       20 : ${ $class.PKG.DECLINED };
  0         0  
154             }
155              
156             sub reason {
157 27     27 1 42 my $self = shift;
158 27   33     53 my $class = ref $self || $self;
159 27   50     63 my $type = reftype $self || BLANK;
160 70     70   428 no strict REFS;
  70         161  
  70         46387  
161             return $type eq HASH
162             ? $self->{ ERROR }
163 27 50       128 : ${ $class.PKG.ERROR };
  0         0  
164             }
165              
166             sub throw {
167 61     61 1 77 my $self = shift;
168 61         72 my $type = shift;
169 61         166 my $emod = $self->exception;
170 61         90 my $e;
171              
172             # TODO: grok file/line/sub from caller and add to exceptions
173              
174 61 100       112 if (! @_) {
175             # single argument can be an exception object or an error message
176             # which is given whatever type is returned by throws()
177             #
178             # throw($exception)
179             # throw($info)
180              
181 58 50 33     171 if (blessed $type && $type->isa($emod)) {
182 0         0 $self->debug("returning exception object: ", ref $type, " => [$type]\n") if DEBUG;
183 0         0 $e = $type;
184             }
185             else {
186 58         60 $self->debug("creating new exception object chain: info => $type\n") if DEBUG;
187 58         179 $e = $emod->new( type => $self->throws, info => $type );
188             }
189             }
190             else {
191             # Next argument can also be an exception object (e.g. when chaining
192             # exceptions) or a regular info message. In the first case, we don't
193             # re-throw the exception if it's already of the correct $type (but we
194             # do if any extra arguments are provided)
195             #
196             # throw($type, $exception)
197             # throw($type, $info)
198              
199 3         4 my $info = shift;
200              
201 3 100 66     30 if (! @_ && blessed $info && $info->isa($emod) && $info->type eq $type) {
      66        
      100        
202             # second argument is already an exception of type $type
203 1         3 $e = $info;
204             }
205             else {
206 2 50 33     8 my $config = @_ && ref $_[0] eq HASH ? shift : { @_ };
207             # construct a new exception from $type and $info fields
208 2         4 $config->{ type } = $type;
209 2         3 $config->{ info } = $info;
210 2         2 $self->debug("creating new exception object: ", $self->dump_hash($config), "\n") if DEBUG;
211 2         5 $e = $emod->new($config);
212             }
213             }
214 61         144 $e->throw;
215             }
216              
217             sub try {
218 294     294 1 349 my $self = shift;
219 294 100       402 if (@_) {
220 16         31 my $method = shift;
221 16 100       27 if (wantarray) {
222 1         2 my @result = eval { $self->$method(@_) };
  1         3  
223 1 50       6 $self->decline($@) if $@;
224 1         3 return @result;
225             }
226             else {
227 15         17 my $result = eval { $self->$method(@_) };
  15         46  
228 15 100       84 $self->decline($@) if $@;
229 15         61 return $result;
230             }
231             }
232             else {
233 278         431 return TRIAL->_bind_($self);
234             }
235             }
236              
237             sub catch {
238             # this depends on some code in Badger::Exception which I haven't
239             # written yet...
240 0     0 1 0 shift->todo;
241             }
242              
243             sub throws {
244 65     65 1 96 my $self = shift;
245 65   100     181 my $type = reftype $self || BLANK;
246 65         128 my $class = class($self);
247 65         81 my $throws;
248              
249 65 100       187 if (@_) {
    100          
250             # hash objects store exception type in $self->{ THROWS }, anything
251             # else (classes and non-hash objects) use the $THROWS package var
252             $throws = $type eq HASH
253             ? ($self->{ THROWS } = shift)
254 4 100       18 : $class->var(THROWS, shift);
255             }
256             elsif ($type eq HASH) {
257             # we also look in $self->{ config } to see if a 'throws' was
258             # specified as a constructor argument.
259             $throws = $self->{ THROWS }
260             ||= $self->{ config }
261 51   66     249 && $self->{ config }->{ throws };
      100        
262             }
263              
264             # fall back on looking for any package variable in class / base classes
265 65   66     209 return $throws
266             || $class->any_var(THROWS)
267             || $class->id;
268             }
269              
270             sub exception {
271 61     61 1 72 my $self = shift;
272 61   100     185 my $type = reftype $self || BLANK;
273 61         60 my $emod;
274              
275             # TODO: Move this into Template::Class. It's so rare that you want to
276             # set an exception type this way. Then we can have throw() pass the $type
277             # to exception() and allow subclasses to make a decision about what kind
278             # of exception to return based on the $type.
279 61 50       231 if (@_) {
    100          
280             # as per throws() above, we have to be careful to only treat $self
281             # like a hash when it is a hash-based object
282             $emod = $type eq HASH
283             ? ($self->{ EXCEPTION } = shift)
284 0 0       0 : class($self)->var(EXCEPTION, shift);
285             }
286             elsif ($type eq HASH) {
287             $emod = $self->{ EXCEPTION }
288             ||= $self->{ config }
289 55   33     275 && $self->{ config }->{ exception };
      33        
290             }
291 61   33     173 return $emod
292             || class($self)->any_var(EXCEPTION);
293             }
294              
295             sub fatal {
296 3     3 1 8 my $self = shift;
297 3   66     20 my $class = ref $self || $self;
298 3         9 my $error = join(BLANK, @_);
299 70     70   498 no strict REFS;
  70         110  
  70         83293  
300              
301             # set package variable
302 3         6 ${ $class.PKG.ERROR } = $error;
  3         12  
303              
304 3 100 66     17 if (ref $self && reftype $self eq HASH) {
305 2         6 $self->{ ERROR } = $error;
306 2         5 $self->{ DECLINED } = 0;
307             }
308              
309 3         22 require Carp;
310 3         492 Carp::confess("Fatal badger error: ", @_);
311             }
312              
313              
314             #-----------------------------------------------------------------------
315             # messages
316             #-----------------------------------------------------------------------
317              
318             sub message {
319 1862     1862 1 1889 my $self = shift;
320 1862   33     3024 my $name = shift
321             || $self->fatal("message() called without format name");
322 1862   66     5762 my $ref = $self && reftype $self;
323 1862         1803 my $format;
324              
325             # allow $self object to have an internal messages hash
326 1862 100 66     8252 if ($self && $ref && $ref eq HASH && $self->{ messages }) {
      66        
      100        
327             $format = $self->{ messages }->{ $name }
328 1 50       5 if reftype $self->{ messages } eq HASH;
329             }
330              
331 1862 100 33     4287 $format = class($self)->hash_value( MESSAGES => $name )
332             || $self->fatal("message() called with invalid message type: $name")
333             unless defined $format;
334              
335 1862         4104 xprintf($format, @_);
336             }
337              
338             sub warn_msg {
339             # explicitly quantify local message() method in case a subclass decides
340             # to re-implement the message() method to do something else
341 1     1 1 7 $_[0]->warn( message(@_) );
342             }
343              
344             sub error_msg {
345 26     26 1 82 $_[0]->error( message(@_) );
346             }
347              
348             sub fatal_msg {
349 0     0 1 0 $_[0]->fatal( message(@_) );
350             }
351              
352             sub decline_msg {
353 11     11 1 29 $_[0]->decline( message(@_) );
354             }
355              
356             sub debug_msg {
357 0     0 1 0 $_[0]->debug( message(@_) );
358             }
359              
360             sub throw_msg {
361 1     1 1 6 my $self = shift;
362 1         4 $self->throw( shift, message($self, @_) );
363             }
364              
365              
366             #-----------------------------------------------------------------------
367             # generate not_implemented() and todo() methods
368             #-----------------------------------------------------------------------
369              
370             class->methods(
371             map {
372             my $name = $_;
373             $name => sub {
374 10     10   107 my $self = shift;
375 10   33     16 my $ref = ref $self || $self;
376 10         54 my ($pkg, $file, $line, $sub) = caller(0);
377 10         36 $sub = (caller(1))[3]; # subroutine the caller was called from
378 10         46 $sub =~ s/(.*):://;
379 10 100       21 my $msg = @_ ? join(BLANK, SPACE, @_) : BLANK;
380 10         39 return $self->error_msg( $name => "$sub()$msg", "for $ref in $file at line $line" );
381             };
382             }
383             qw( not_implemented todo )
384             );
385              
386              
387             #-----------------------------------------------------------------------
388             # generate on_warn() and on_error() methods
389             #-----------------------------------------------------------------------
390              
391             class->methods(
392             map {
393             my $on_event = $_;
394             my $ON_EVENT = uc $on_event;
395              
396             $on_event => sub {
397 69     69   95 my $self = shift;
398 69         153 my $class = class($self);
399 69         83 my $list;
400              
401 69 100 66     269 if (ref $self && reftype $self eq HASH) {
402             # look in $self->{ config }->{ on_xxx } or in $ON_XXX pkg
403             # var for one or more event handlers
404             $list = $self->{ $ON_EVENT }
405 61   66     287 ||= $self->{ config }->{ $on_event }
      66        
406             || $class->list_vars($ON_EVENT);
407             # careful! the config value might be a single handler
408 61 100       128 $list = $self->{ $ON_EVENT } = [$list]
409             unless ref $list eq ARRAY;
410 61         60 $self->debug("got $on_event handlers: ", $self->dump_data_inline($list), "\n") if DEBUG;
411             }
412             else {
413             # class method or non-hash objects use pkg vars only
414 8         36 $list = $class->var_default($ON_EVENT, []);
415 8 100       29 $list = $class->var($ON_EVENT, [$list])
416             unless ref $list eq ARRAY;
417             }
418              
419             # Add to the list any extra handlers passed as args. First
420             # argument can be 'before' or 'after' to add remaining args
421             # to start or end of list, otherwise the entire list is replaced.
422 69 100       162 if (@_) {
423 3 50       10 if ($_[0] eq before) {
    100          
424 0         0 shift;
425 0         0 unshift(@$list, @_);
426             }
427             elsif ($_[0] eq after) {
428 1         1 shift;
429 1         3 push(@$list, @_);
430             }
431             else {
432 2         4 @$list = @_;
433             }
434             }
435             # push(@$list, @_);
436              
437 69         95 return $list;
438             }
439             }
440             qw( on_warn on_error )
441             );
442              
443              
444             #-----------------------------------------------------------------------
445             # internal method to dispatch on_error/on_warning handlers
446             #-----------------------------------------------------------------------
447              
448             sub _dispatch_handlers {
449 10     10   19 my ($self, $type, $handlers, @args) = @_;
450              
451 10         11 $self->debug("_dispatch handlers: ", $self->dump_data_inline($handlers), "\n") if DEBUG;
452              
453 10         16 foreach (@$handlers) {
454 12         11 my $handler = $_; # don't alias list items
455 12         9 $self->debug("dispatch handler: $handler\n") if DEBUG;
456 12 100       23 if (! ref $handler) {
    50          
457 6 100       12 if ($handler eq WARN) { # 'warn' - we make sure that the
    50          
458 2         4 my $msg = join('', @args); # message is newline terminated
459 2         5 chomp($msg); # to stop Perl from adding a line
460 2         23 CORE::warn $msg, "\n"; # number that'll be wrong.
461             }
462             elsif ($handler eq NONE) { # NONE/0 - bail out
463 0         0 last;
464             }
465             else {
466 4   100     42 $handler = $self->can($handler)
467             || return $self->fatal("Invalid on_$type method: $handler");
468 3         7 @args = $handler->($self, @args);
469             }
470             }
471             elsif (ref $handler eq CODE) {
472 6         14 @args = $handler->(@args);
473             }
474             else {
475 0         0 $self->fatal("Invalid on_$type handler: $handler");
476             }
477 10         48 $self->debug("mid-dispatch args: [", join(', ', @args), "]\n") if DEBUG;
478             # bail out if we got an empty list of return values or a single
479             # false value
480 10 100 66     53 last if ! @args || @args == 1 && ! $args[0];
      66        
481             }
482 8         9 $self->debug("returning ", join(', ', @args), "\n") if DEBUG;
483 8         15 return @args;
484             }
485              
486              
487              
488             #-----------------------------------------------------------------------
489             # Badger::Base::Trial - nomadic object for $object->try operation
490             #-----------------------------------------------------------------------
491              
492             package Badger::Base::Trial;
493             our $AUTOLOAD;
494              
495             sub _bind_ {
496 278     278   347 my ($class, $object) = @_;
497 278   33     1397 bless \$object, ref $class || $class;
498             }
499              
500             sub AUTOLOAD {
501 556     556   643 my $self = shift;
502 556         2206 my ($method) = ($AUTOLOAD =~ /([^:]+)$/ );
503 556 100       1291 return if $method eq 'DESTROY';
504              
505             # call method on target object in eval block, and downgrade
506 278 100       353 if (wantarray) {
507 1         2 my @result = eval { $$self->$method(@_) };
  1         6  
508 1 50       6 $$self->decline($@) if $@;
509 1         4 return @result;
510             }
511             else {
512 277         263 my $result = eval { $$self->$method(@_) };
  277         584  
513 277 100       442 $$self->decline($@) if $@;
514 277         1239 return $result;
515             }
516              
517             # TODO: catch missing error methods
518             }
519              
520              
521             1;
522             __END__