File Coverage

blib/lib/Term/Emit.pm
Criterion Covered Total %
statement 321 356 90.1
branch 162 212 76.4
condition 70 98 71.4
subroutine 34 48 70.8
pod 25 25 100.0
total 612 739 82.8


line stmt bran cond sub pod time code
1             # Term::Emit - Print with indentation, status, and closure
2             #
3             # $Id: Emit.pm 395 2012-09-06 18:21:50Z steve $
4              
5             package Term::Emit;
6 15     15   367362 use warnings;
  15         33  
  15         541  
7 15     15   83 use strict;
  15         30  
  15         492  
8 15     15   370 use 5.008;
  15         58  
  15         636  
9              
10 15     15   79 use Exporter;
  15         26  
  15         837  
11 15     15   88 use base qw/Exporter/;
  15         27  
  15         2206  
12 15     15   14572 use Scope::Upper 0.06 qw/:words reap/;
  15         18377  
  15         3887  
13              
14             our $VERSION = '0.0.4';
15             our @EXPORT_OK = qw/emit emit_over emit_prog emit_text emit_done emit_none
16             emit_emerg
17             emit_alert
18             emit_crit emit_fail emit_fatal
19             emit_error
20             emit_warn
21             emit_note
22             emit_info emit_ok
23             emit_debug
24             emit_notry
25             emit_unk
26             emit_yes
27             emit_no/;
28             our %EXPORT_TAGS = (all => [@EXPORT_OK]);
29              
30 15     15   111 use constant MIN_SEV => 0;
  15         30  
  15         1716  
31 15     15   78 use constant MAX_SEV => 15;
  15         29  
  15         6938  
32             our %SEVLEV = (
33             EMERG => 15,
34             ALERT => 13,
35             CRIT => 11,
36             FAIL => 11,
37             FATAL => 11,
38             ERROR => 9,
39             WARN => 7,
40             NOTE => 6,
41             INFO => 5,
42             OK => 5,
43             DEBUG => 4,
44             NOTRY => 3,
45             UNK => 2,
46             OTHER => 1,
47             YES => 1,
48             NO => 0,
49             );
50             our %BASE_OBJECT = ();
51              
52             sub new {
53 18     18 1 36 my $proto = shift;
54 18   33     121 my $class = ref($proto) || $proto; # Get the class name
55 18         82 my $this = {
56             pos => 0, # Current output column number
57             progwid => 0, # Width of last progress message emitted
58             msgs => []
59             }; # Closing message stack
60 18         49 bless $this, $class;
61 18         164 $this->setopts(@_);
62 18         78 return $this;
63             }
64              
65             sub base {
66 0     0 1 0 my ($this) = _process_args(@_);
67 0         0 return $this;
68             }
69              
70             sub clone {
71 3     3 1 5 my $this = shift; # Object to clone
72 3         4 return Term::Emit->new(%{$this}, _clean_opts(@_));
  3         23  
73             }
74              
75             sub import {
76 15     15   244 my $class = shift;
77              
78             # Yank option sets, if any, out from the arguments
79 15         35 my %opts = ();
80 15         33 my @args = ();
81 15         98 while (@_) {
82 26         47 my $arg = shift;
83 26 100       93 if (ref($arg) eq 'HASH') {
84 13         38 %opts = (%opts, %{$arg}); #merge
  13         56  
85 13         54 next;
86             }
87 13         50 push @args, $arg;
88             }
89 15         64 %opts = _clean_opts(%opts);
90              
91             # Create the default base object
92 15   33     155 $BASE_OBJECT{0} ||= new Term::Emit(%opts);
93              
94             # Continue exporter's work
95 15         39640 return $class->export_to_level(1, $class, @args);
96             }
97              
98             #
99             # Set options
100             #
101             sub setopts {
102 36     36 1 11371 my ($this, $opts, %args) = _process_args(@_);
103              
104             # Merge & clean 'em
105 36         82 %args = (%{$opts}, %args);
  36         170  
106 36         123 %args = _clean_opts(%args); ###why does this not work here?? -fh vs fh
107              
108             # Process args
109 36         155 my $deffh = select();
110 15     15   101 no strict 'refs';
  15         35  
  15         888  
111             $this->{fh}
112             = $args{fh}
113             || $this->{fh}
114 36   100     343 || \*{$deffh};
115 15     15   261 use strict 'refs';
  15         36  
  15         76613  
116 36   100     367 $this->{envbase}
117             = $args{envbase}
118             || $this->{envbase}
119             || 'term_emit_fd'; ### TODO: apply to all envvars we use, not just _fd
120 36 100       233 $this->{bullets}
    100          
    50          
121             = exists $ENV{term_emit_bullets} ? $ENV{term_emit_bullets}
122             : exists $args{bullets} ? $args{bullets}
123             : exists $this->{bullets} ? $this->{bullets}
124             : 0;
125 36   100     286 $this->{closestat}
126             = $args{closestat}
127             || $this->{closestat}
128             || 'DONE';
129 36 50 50     346 $this->{color}
130             = exists $ENV{term_emit_color}
131             ? $ENV{term_emit_color}
132             : $args{color}
133             || $this->{color}
134             || 0;
135 36 50 100     312 $this->{ellipsis}
136             = exists $ENV{term_emit_ellipsis}
137             ? $ENV{term_emit_ellipsis}
138             : $args{ellipsis}
139             || $this->{ellipsis}
140             || '...';
141 36 100       171 $this->{maxdepth}
    50          
142             = exists $ENV{term_emit_maxdepth} ? $ENV{term_emit_maxdepth}
143             : exists $args{maxdepth} ? $args{maxdepth}
144             : $this->{maxdepth}; #undef=all, 0=none, 3=just first 3 levels, etc
145 36 100       168 $this->{showseverity}
    50          
146             = exists $ENV{term_emit_showseverity} ? $ENV{term_emit_showseverity}
147             : exists $args{showseverity} ? $args{showseverity}
148             : $this->{showseverity};
149 36 100       237 $this->{step}
    100          
    50          
150             = exists $ENV{term_emit_step} ? $ENV{term_emit_step}
151             : exists $args{step} ? $args{step}
152             : defined $this->{step} ? $this->{step}
153             : 2;
154 36   100     252 $this->{timestamp} = $args{timestamp}
155             || $this->{timestamp}
156             || 0;
157 36 50 100     296 $this->{trailer}
158             = exists $ENV{term_emit_trailer}
159             ? $ENV{term_emit_trailer}
160             : $args{trailer}
161             || $this->{trailer}
162             || q{.};
163 36 50 100     263 $this->{width}
164             = exists $ENV{term_emit_width}
165             ? $ENV{term_emit_width}
166             : $args{width}
167             || $this->{width}
168             || 80;
169              
170             # $this->{timefmt} = $args{timefmt} || $this->{timefmt} || undef; # Timestamp format
171             # $this->{pos} = $args{pos}
172             # if defined $args{pos};
173              
174             # Recompute a few things
175             # TODO: Allow bullets to be given as CSV: "* ,+ ,- , " for example.
176             # TODO: Put this in a sub of its own.
177 36         81 $this->{bullet_width} = 0;
178 36 100       256 if (ref $this->{bullets} eq 'ARRAY') {
    50          
179 2         2 foreach my $b (@{$this->{bullets}}) {
  2         18  
180 8 100       27 $this->{bullet_width} = length($b)
181             if length($b) > $this->{bullet_width};
182             }
183             }
184             elsif ($this->{bullets}) {
185 0         0 $this->{bullet_width} = length($this->{bullets});
186              
187 0         0 return 0;
188             }
189             }
190              
191             #
192             # Emit a message, starting a new level
193             #
194             sub emit {
195 288     288 1 89498 my ($this, $opts, @args) = _process_args(@_);
196 288 50       757 my $jn = defined $, ? $, : q{};
197 288 100 66     1239 if (@args && ref($args[0]) eq 'ARRAY') {
198              
199             # Using [opentext, closetext] notation
200 4         4 my $pair = shift @args;
201 4   50     10 my $otext = $pair->[0] || '';
202 4   33     9 my $ctext = $pair->[1] || $otext;
203 4         7 unshift @args, $otext;
204 4         8 $opts->{closetext} = $ctext;
205             }
206 288         571 my $msg = join $jn, @args;
207 288 50       1155 if (!@args) {
208              
209             # Use our caller's subroutine name as the message
210 0         0 (undef, undef, undef, $msg) = caller(1);
211 0         0 $msg =~ s{^main::}{}sxm;
212             }
213              
214             # Tied closure:
215             # If we're returning into a list context,
216             # then we're tying closure to the scope of the caller's list element.
217 288 50       537 return Term::Emit::TiedClosure->new($this, $opts, $msg)
218             if wantarray;
219              
220             # Store context
221 288 100       668 my $cmsg
222             = defined $opts->{closetext}
223             ? $opts->{closetext}
224             : $msg;
225 288         304 push @{$this->{msgs}}, [$msg, $cmsg];
  288         1049  
226 288   100     817 my $level = $ENV{$this->_envvar()}++ || 0;
227              
228             # Setup the scope reaper for autoclosure
229             reap sub {
230 288     288   1311 $this->emit_done({%{$opts}, want_level => $level}, $this->{closestat});
  288         1424  
231 288         2272 } => SCOPE(1);
232              
233             # Filtering by level?
234 288 100 100     988 return 1
235             if defined($this->{maxdepth}) && $level >= $this->{maxdepth};
236              
237             # Start back at the left
238 257         307 my $s = 1;
239 257 100       633 $s = $this->_spew("\n")
240             if $this->{pos};
241 257 50       489 return $s unless $s;
242 257         334 $this->{pos} = 0;
243 257         353 $this->{progwid} = 0;
244              
245             # Level adjust?
246 257 50 33     631 $level += $opts->{adjust_level}
247             if $opts->{adjust_level} && $opts->{adjust_level} =~ m{^-?\d+$}sxm;
248              
249             # Timestamp
250 257 50       691 my $tsr = defined $opts->{timestamp}? $opts->{timestamp} : $this->{timestamp};
251 257 100 100     606 $tsr = \&_timestamp if $tsr && !ref($tsr);
252 257 100       481 my $ts = $tsr? &$tsr($level) : q{};
253              
254             # The message
255 257         551 my $bullet = $this->_bullet($level);
256 257         627 my $indent = q{ } x ($this->{step} * $level);
257 257         286 my $tlen = 0;
258 257         534 my $span = $this->{width} - length($ts) - length($bullet) - ($this->{step} * $level) - 10;
259 257         871 my @mlines = _wrap($msg, int($span * 2 / 3), $span);
260 257         853 while (defined(my $txt = shift @mlines)) {
261 281         816 $s = $this->_spew($ts . $bullet . $indent . $txt);
262 281 50       624 return $s unless $s;
263 281 100       775 $s = $this->_spew(@mlines ? "\n" : $this->{ellipsis});
264 281 50       610 return $s unless $s;
265 281         369 $tlen = length($txt);
266 281         490 $bullet = q{ } x $this->{bullet_width}; # Only bullet the first line
267 281         900 $ts = q{ } x length($ts); # Only timestamp the first line
268             }
269 257         636 $this->{pos} += length($ts) + ($this->{step} * $level) + length($bullet) + $tlen + length($this->{ellipsis});
270 257         855 return 1;
271             }
272              
273             #
274             # Complete the current level, with status
275             #
276             sub emit_done {
277 493     493 1 1179 my ($this, $opts, @args) = _process_args(@_);
278 493         817 my $want_level = $opts->{want_level};
279 493   100     1108 my $sev = shift @args || 'DONE';
280 493 100       1313 my $sevlev = defined $SEVLEV{uc $sev}? $SEVLEV{uc $sev} : $SEVLEV{'OTHER'};
281              
282             # Test that we're at the right level - do this BEFORE changing the envvar
283 493   100     1113 my $ret_level = ($ENV{$this->_envvar()} || 0) - 1;
284             return
285 493 100 100     3079 if defined $want_level && $ret_level != $want_level;
286              
287             # Decrement level
288 288 50       532 return $sevlev
289             if !$ENV{$this->_envvar()};
290 288         933 my $level = --$ENV{$this->_envvar()};
291 288 100       1007 delete $ENV{$this->_envvar()}
292             if $level <= 0;
293              
294             # Filtering - level & severity
295 288 100       887 my $showseverity
    50          
296             = defined $opts->{showseverity} ? $opts->{showseverity}
297             : defined($this->{showseverity}) ? $this->{showseverity}
298             : MAX_SEV;
299 288 100 100     1421 if ( $sevlev < $showseverity
      100        
300             && defined($this->{maxdepth})
301             && $level >= $this->{maxdepth})
302             {
303 26         25 pop @{$this->{msgs}}; # discard it
  26         46  
304 26         125 return $sevlev;
305             }
306              
307             # Are we silently closing this level?
308 262 100       533 if ($opts->{silent}) {
309 28         36 my $s = 1;
310 28 100       140 $s = $this->_spew("\n")
311             if $this->{pos};
312 28 50       60 return $s unless $s;
313 28         38 $this->{pos} = 0;
314 28         33 $this->{progwid} = 0;
315 28         31 pop @{$this->{msgs}}; # discard it
  28         47  
316 28         150 return $sevlev;
317             }
318              
319             # Make the severity text
320 234         386 my $sevstr = " [$sev]\n";
321 234         331 my $slen = 8; # make left justified within max width 3+5
322 234 50       492 $sevstr = " [" . _colorize($sev, $sev) . "]\n"
323             if $this->{color};
324              
325             # Re-issue message if needed
326 234         247 my $msgs = pop @{$this->{msgs}};
  234         560  
327 234         273 my ($omsg, $cmsg) = @{$msgs}; # Opening and closing messages
  234         409  
328             # -(if not the same, force a re-issue)-
329 234 100 100     1355 if ($this->{pos} && ($omsg ne $cmsg)) {
330             # Closing differs from opening, so we need to re-issue with the closing
331 3         6 my $s = $this->_spew("\n");
332 3 50       7 return $s unless $s;
333 3         9 $this->{pos} = 0;
334             }
335 234 100 100     1066 if ($this->{pos}
      100        
336             && defined($this->{maxdepth})
337             && $level >= $this->{maxdepth}) {
338             # This would be level-filtered, but severity overrode it, so we need to re-issue
339 3         6 my $s = $this->_spew("\n");
340 3 50       8 return $s unless $s;
341 3         7 $this->{pos} = 0;
342             }
343 234 100       580 if ($this->{pos} == 0) {
344             # Timestamp
345 76 50       231 my $tsr = defined $opts->{timestamp}? $opts->{timestamp} : $this->{timestamp};
346 76 50 33     181 $tsr = \&_timestamp if $tsr && !ref($tsr);
347 76 50       142 my $ts = $tsr? &$tsr($level) : q{};
348              
349 76         195 my $bullet = $this->_bullet($level);
350 76         169 my $indent = q{ } x ($this->{step} * $level);
351 76         92 my $tlen = 0;
352 76         142 my $span = $this->{width} - length($ts) - ($this->{step} * $level) - 10;
353 76         257 my @mlines = _wrap($cmsg, int($span * 2 / 3), $span);
354 76         214 while (defined(my $txt = shift @mlines)) {
355 83         87 my $s;
356 83         253 $s = $this->_spew($ts . $bullet . $indent . $txt);
357 83 50       206 return $s unless $s;
358 83 100       183 $s = $this->_spew("\n")
359             if @mlines;
360 83 50       158 return $s unless $s;
361 83         106 $tlen = length($txt);
362 83         138 $bullet = q{ } x $this->{bullet_width}; # Only bullet the first line
363 83         281 $ts = q{ } x length($ts); # Only timestamp the first line
364             }
365 76         211 $this->{pos} += length($ts) + length($bullet) + ($this->{step} * $level) + $tlen;
366             }
367              
368             # Trailer
369 234         396 my $ndots = $this->{width} - $this->{pos} - $slen;
370 234         260 my $s = 1;
371 234 50       996 $s = $this->_spew($this->{trailer} x $ndots)
372             if $ndots > 0;
373 234 50       528 return $s unless $s;
374              
375             # Severity
376 234         496 $s = $this->_spew($sevstr);
377 234 50       507 return $s unless $s;
378 234         318 $this->{pos} = 0;
379              
380             # Reason option?
381 234         355 my $reason = $opts->{reason};
382 234         496 $opts->{force} = 1; # Always give reason if we got thru above level filtering
383 234 100       488 $s = emit_text($opts, $reason)
384             if $reason;
385 234 50       420 return $s unless $s;
386              
387             # Return with a severity value
388 234         1397 return $sevlev;
389             }
390              
391             #
392             # Progress output
393             #
394             sub emit_over {
395 2     2 1 1139 my ($this, $opts, @args) = _process_args(@_);
396              
397             # Filtering by level?
398 2   50     8 my $level = $ENV{$this->_envvar()} || 0;
399 2 50 33     10 return 1
400             if defined($this->{maxdepth}) && $level > $this->{maxdepth};
401              
402             # Erase prior progress output
403 2         3 my $s = 1;
404 2         9 $s = $this->_spew(qq{\b} x $this->{progwid});
405 2 50       23 return $s unless $s;
406 2         9 $s = $this->_spew(q{ } x $this->{progwid});
407 2 50       8 return $s unless $s;
408 2         7 $s = $this->_spew(qq{\b} x $this->{progwid});
409 2 50       6 return $s unless $s;
410 2         5 $this->{pos} -= $this->{progwid};
411 2         5 $this->{progwid} = 0;
412              
413 2         5 return $this->emit_prog(@args);
414             }
415              
416             sub emit_prog {
417 4     4 1 1129 my ($this, $opts, @args) = _process_args(@_);
418 4 50       12 my $jn = defined $, ? $, : q{};
419 4         7 my $msg = join $jn, @args;
420 4         4 my $s;
421              
422             # Filtering by level?
423 4   50     10 my $level = $ENV{$this->_envvar()} || 0;
424 4 50 33     13 return 1
425             if defined($this->{maxdepth}) && $level > $this->{maxdepth};
426              
427             # Start a new line?
428 4         10 my $avail = $this->{width} - $this->{pos} - 10;
429 4 50       10 if (length($msg) > $avail) {
430 0         0 my $level = $ENV{$this->_envvar()};
431 0         0 my $bspace = q{ } x $this->{bullet_width};
432 0         0 my $indent = q{ } x ($this->{step} * $level);
433 0         0 $s = $this->_spew("\n");
434 0 0       0 return $s unless $s;
435 0         0 $s = $this->_spew($bspace . $indent);
436 0 0       0 return $s unless $s;
437 0         0 $this->{pos} = length($bspace) + length($indent);
438 0         0 $this->{progwid} = 0;
439             }
440              
441             # The text
442 4         9 $s = $this->_spew($msg);
443 4 50       12 return $s unless $s;
444 4         5 $this->{pos} += length($msg);
445 4         8 $this->{progwid} += length($msg);
446              
447 4         13 return 1;
448             }
449              
450             #
451             # Issue additional info at the current level
452             #
453             sub emit_text {
454 10     10 1 49 my ($this, $opts, @args) = _process_args(@_);
455 10 50       37 my $jn = defined $, ? $, : q{};
456 10         28 my $msg = join $jn, @args;
457              
458             # Filtering by level?
459 10   50     32 my $level = $ENV{$this->_envvar()} || 0;
460 10 50 66     72 return 1
      33        
461             if !$opts->{force} && defined($this->{maxdepth}) && $level > $this->{maxdepth};
462              
463             # Start a new line
464 10         20 my $s = 1;
465 10 100       43 $s = $this->_spew("\n")
466             if $this->{pos};
467 10 50       31 return $s unless $s;
468              
469             # Level adjust?
470 10         19 $level++; # We're over by one by default
471 10 50 33     48 $level += $opts->{adjust_level}
472             if $opts->{adjust_level} && $opts->{adjust_level} =~ m{^-?\d+$}sxm;
473              
474             # Emit the text
475 10         37 my $indent = q{ } x ($this->{step} * $level);
476 10         27 my $span = $this->{width} - ($this->{step} * $level) - 10;
477 10         42 my @mlines = _wrap($msg, int($span * 2 / 3), $span);
478 10         56 while (defined(my $txt = shift @mlines)) {
479 10         26 my $bspace = q{ } x $this->{bullet_width};
480 10         47 $s = $this->_spew($bspace . $indent . $txt . "\n");
481 10 50       34 return $s unless $s;
482 10         49 $this->{pos} = 0;
483             }
484 10         50 return 1;
485             }
486              
487 0     0 1 0 sub emit_emerg {emit_done @_, "EMERG"}; # syslog: Off the scale!
488 0     0 1 0 sub emit_alert {emit_done @_, "ALERT"}; # syslog: A major subsystem is unusable.
489 19     19 1 162 sub emit_crit {emit_done @_, "CRIT"}; # syslog: a critical subsystem is not working entirely.
490 0     0 1 0 sub emit_fail {emit_done @_, "FAIL"}; # Failure
491 0     0 1 0 sub emit_fatal {emit_done @_, "FATAL"}; # Fatal error
492 0     0 1 0 sub emit_error {emit_done @_, "ERROR"}; # syslog 'err': Bugs, bad data, files not found, ...
493 21     21 1 84 sub emit_warn {emit_done @_, "WARN"}; # syslog 'warning'
494 0     0 1 0 sub emit_note {emit_done @_, "NOTE"}; # syslog 'notice'
495 0     0 1 0 sub emit_info {emit_done @_, "INFO"}; # syslog 'info'
496 32     32 1 213 sub emit_ok {emit_done @_, "OK"}; # copacetic
497 0     0 1 0 sub emit_debug {emit_done @_, "DEBUG"}; # syslog: Really boring diagnostic output.
498 6     6 1 26 sub emit_notry {emit_done @_, "NOTRY"}; # Untried
499 0     0 1 0 sub emit_unk {emit_done @_, "UNK"}; # Unknown
500 0     0 1 0 sub emit_yes {emit_done @_, "YES"}; # Yes
501 0     0 1 0 sub emit_no {emit_done @_, "NO"}; # No
502 5     5 1 80 sub emit_none {emit_done {-silent => 1}, @_, "NONE"}
503             # *Special* closes level quietly (prints no wrapup severity)
504              
505             #
506             # Return the bullet string for the given level
507             #
508             sub _bullet {
509 333     333   434 my ($this, $level) = @_;
510 333         447 my $bullet = q{};
511 333 100       912 if (ref($this->{bullets}) eq 'ARRAY') {
    50          
512 70         62 my $pmax = $#{$this->{bullets}};
  70         108  
513 70 100       166 $bullet = $this->{bullets}->[$level > $pmax ? $pmax : $level];
514             }
515              
516             # TODO: Allow bullets to be given as CSV: "* ,+ ,- , " for example.
517             elsif ($this->{bullets}) {
518 0         0 $bullet = $this->{bullets};
519             }
520             else {
521 263         557 return q{};
522             }
523 70         139 my $pad = q{ } x ($this->{bullet_width} - length($bullet));
524 70         152 return $bullet . $pad;
525             }
526              
527             #
528             # Clean option keys
529             #
530             sub _clean_opts {
531 383     383   826 my %in = @_;
532 383         555 my %out = ();
533 383         708 foreach my $k (keys %in) {
534 491         659 my $v = $in{$k};
535 491         696 delete $in{$k};
536 491         2135 $k =~ s{^\s*-?(\w+)\s*}{$1}sxm;
537 491         1633 $out{lc $k} = $v;
538             }
539 383         1763 return %out;
540             }
541              
542             #
543             # Add ANSI color to a string, if ANSI is enabled
544             ### TODO: use Term::ANSIColor, a standard module (verify what perl version introduced it, tho)
545             #
546             sub _colorize {
547 17     17   9513 my ($str, $sev) = @_;
548 17         22 my $zon = q{};
549 17         61 my $zoff = q{};
550 17 100       70 $zon = chr(27) . '[1;31;40m' if $sev =~ m{\bEMERG(ENCY)?}i; #bold red on black
551 17 100       41 $zon = chr(27) . '[1;35m' if $sev =~ m{\bALERT\b}i; #bold magenta
552 17 100       57 $zon = chr(27) . '[1;31m' if $sev =~ m{\bCRIT(ICAL)?\b}i; #bold red
553 17 100       42 $zon = chr(27) . '[1;31m' if $sev =~ m{\bFAIL(URE)?\b}i; #bold red
554 17 100       38 $zon = chr(27) . '[1;31m' if $sev =~ m{\bFATAL\b}i; #bold red
555 17 100       44 $zon = chr(27) . '[31m' if $sev =~ m{\bERR(OR)?\b}i; #red
556 17 100       35 $zon = chr(27) . '[33m' if $sev =~ m{\bWARN(ING)?\b}i; #yellow
557 17 100       40 $zon = chr(27) . '[36m' if $sev =~ m{\bNOTE\b}i; #cyan
558 17 100       42 $zon = chr(27) . '[32m' if $sev =~ m{\bINFO(RMATION)?\b}i; #green
559 17 100       42 $zon = chr(27) . '[1;32m' if $sev =~ m{\bOK\b}i; #bold green
560 17 100       38 $zon = chr(27) . '[37;43m' if $sev =~ m{\bDEBUG\b}i; #grey on yellow
561 17 100       33 $zon = chr(27) . '[30;47m' if $sev =~ m{\bNOTRY\b}i; #black on grey
562 17 100       44 $zon = chr(27) . '[1;37;47m' if $sev =~ m{\bUNK(OWN)?\b}i; #bold white on gray
563 17 100       38 $zon = chr(27) . '[32m' if $sev =~ m{\bYES\b}i; #green
564 17 100       45 $zon = chr(27) . '[31m' if $sev =~ m{\bNO\b}i; #red
565 17 100       32 $zoff = chr(27) . '[0m' if $zon;
566 17         163 return $zon . $str . $zoff;
567             }
568              
569             #
570             # The level's envvar for this filehandle
571             #
572             sub _envvar {
573 1546     1546   1939 my $this = shift;
574 1546         2995 return $this->{envbase} . _oid($this->{fh});
575             }
576              
577             # Return an output identifier for the filehandle
578             #
579             sub _oid {
580 2386     2386   2698 my $fh = shift;
581 2386 100       11543 return 'str' if ref($fh) eq 'SCALAR';
582 854 100       1902 return 0 if ref($fh);
583 523   100     4219 return fileno($fh || q{}) || 0;
584             }
585              
586             #
587             # Figure out what was passed to us
588             #
589             # Each $BASE_OBJECT in the hash is associated with one output ID (the oid).
590             # The oid is just the fileno() of the file handle for normal output,
591             # or the special text "str" when output is to a scalar string reference.
592             # That's why we use a base object _hash_ instead of an array.
593             # The $BASE_OBJECT{0} is the default one. It's equivalent to whatever
594             # oid was specified in the "use Term::Emit ... {-fh=>$blah}" which typically
595             # is STDOUT (oid=1) but may be anything.
596             #
597             # So what're we doing here? We have to figure out which base object to use.
598             # Our subs can be called four ways:
599             # A) emit "blah";
600             # B) emit *LOG, "blah";
601             # C) $tobj->emit "blah";
602             # D) $tobj->emit *LOG, "blah";
603             # Also note that "emit {-fh=>*LOG},..." is considered equivalent to case B,
604             # while "$tobj->emit {-fh=>*LOG},..." is considered equivalent to case D.
605             #
606             # In case A, we simply use the default base object $BASE_OBJECT{0}.
607             # In case B, we get the oid of *LOG and use that base object.
608             # If the base object does not exist, then we make one,
609             # cloning it from base object 0 but overriding with the file handle.
610             # In case C, we use the base object $tobj - this is classic OO perl.
611             # In case D, it's like case B except that if we have to make a new
612             # base object, we clone from $tobj instead of base object 0.
613             #
614             sub _process_args {
615 833 100   833   2110 my $this = ref($_[0]) eq __PACKAGE__ ? shift : $BASE_OBJECT{0};
616 833         1545 my $oid = _oid($_[0]);
617 833 100       1733 if ($oid) {
618              
619             # We're given a filehandle or scalar ref for output.
620             # Find the associated base object or make a new one for it
621 12         30 my $fh = shift;
622 12 100       78 if ($fh eq $BASE_OBJECT{0}->{fh}) {
    100          
623              
624             # Use base object 0, 'cuz it matches
625 6         11 $oid = 0;
626             }
627             elsif (!exists $BASE_OBJECT{$oid}) {
628 3         16 $BASE_OBJECT{$oid} = $this->clone(-fh => $fh);
629             }
630 12         86 $this = $BASE_OBJECT{$oid};
631             }
632 833         1224 my $opts = {};
633 833 100       1730 if (ref($_[0]) eq 'HASH') {
634 329         337 $opts = {_clean_opts(%{shift()})};
  329         951  
635             }
636 833         2720 return ($this, $opts, @_);
637             }
638              
639             #
640             # Emit output to filehandle, string, whatever...
641             #
642             sub _spew {
643 1239     1239   1613 my $this = shift;
644 1239         1509 my $out = shift;
645 1239         1783 my $fh = $this->{fh};
646 1239 100       2406 return ref($fh) eq 'SCALAR' ? ${$fh} .= $out : print {$fh} $out;
  1221         4000  
  18         460  
647             }
648              
649             #
650             # Default timestamp
651             #
652             sub _timestamp {
653 2     2   39 my $level = shift; #fwiw
654 2         218 my ($s, $m, $h) = localtime(time());
655 2         12 return sprintf "%2.2d:%2.2d:%2.2d ", $h, $m, $s;
656             }
657              
658             #
659             # Wrap text to fit within line lengths
660             # (Do we want to delete this and add a dependency to Text::Wrap ??)
661             #
662             sub _wrap {
663 349     349   5908 my ($msg, $min, $max) = @_;
664 349 100 100     2409 return ($msg)
      100        
665             if !defined $msg
666             || $max < 3
667             || $min > $max;
668              
669             # First split on newlines
670 346         581 my @lines = ();
671 346         947 foreach my $line (split(/\n/, $msg)) {
672 346         431 my $split = $line;
673              
674             # Then if each segment is more than the width, wrap it
675 346         782 while (length($split) > $max) {
676              
677             # Look backwards for whitespace to split on
678 34         35 my $pos = $max;
679 34         69 while ($pos >= $min) {
680 154 100       672 if (substr($split, $pos, 1) =~ m{\s}sxm) {
681 29         30 $pos++;
682 29         34 last;
683             }
684 125         186 $pos--;
685             }
686 34 100       66 $pos = $max if $pos < $min; #no good place to break, use the max
687              
688             # Break it
689 34         56 my $chunk = substr($split, 0, $pos);
690 34         124 $chunk =~ s{\s+$}{}sxm;
691 34         50 push @lines, $chunk;
692 34         100 $split = substr($split, $pos, length($split) - $pos);
693             }
694 346         1126 $split =~ s{\s+$}{}sxm; #trim
695 346         926 push @lines, $split;
696             }
697 346         1157 return @lines;
698             }
699              
700             ### O ###
701              
702             package Term::Emit::TiedClosure;
703              
704             sub new {
705 0     0     my ($proto, $base, @args) = @_;
706 0   0       my $class = ref($proto) || $proto; # Get the class name
707 0           my $this = {-base => $base};
708 0           bless($this, $class);
709 0           $base->emit(@args);
710 0           return $this;
711             }
712              
713             sub DESTROY {
714 0     0     my $this = shift;
715 0           return $this->{-base}->emit_done();
716             }
717              
718             1; # EOM
719             __END__