document available palette parameter names
[barcat.git] / barcat
1 #!/usr/bin/perl -CA
2 use 5.014;
3 use warnings;
4 use utf8;
5 use List::Util qw( min max sum );
6 use open qw( :std :utf8 );
7 use re '/msx';
8
9 our $VERSION = '1.09';
10
11 my %opt;
12 if (@ARGV) {
13 require Getopt::Long;
14 Getopt::Long->import('2.33', qw( :config gnu_getopt ));
15 GetOptions(\%opt,
16         'ascii|a!',
17         'color|C!',
18         'M' => sub { $opt{color} = 0 },
19         'field|f=s' => sub {
20                 eval {
21                         local $_ = $_[1];
22                         s/\A[0-9]+\z/(?:\\S*\\h+){$_}\\K/;
23                         s{\A[+]([0-9]*)\z}{
24                                 (!!$1 && '(?:\d+\D+\b){'.$1.'}\K') . '\s* (?=\d)'
25                         }e;
26                         $opt{anchor} = qr/$_/;
27                 } or die $@ =~ s/(?:\ at\ \N+)?\Z/ for option $_[0]/r;
28         },
29         'human-readable|H!',
30         'sexagesimal!',
31         'reformat!',
32         'interval|t:i',
33         'trim|length|l=s' => sub {
34                 my ($optname, $optval) = @_;
35                 $optval =~ s/%$// and $opt{trimpct}++;
36                 $optval =~ m/\A-?[0-9]+\z/ or die(
37                         "Value \"$optval\" invalid for option $optname",
38                         " (number or percentage expected)\n"
39                 );
40                 $opt{trim} = $optval;
41         },
42         'value-length=i',
43         'minval=f',
44         'maxval=f',
45         'limit|L:s' => sub {
46                 my ($optname, $optval) = @_;
47                 $optval ||= 0;
48                 $optval =~ /\A-[0-9]+\z/ and $optval .= '-';  # tail shorthand
49                 $optval =~ s/[+]/--/;
50                 my ($start, $end) =
51                 $optval =~ m/\A (?: (-? [0-9]+)? - )? (-? [0-9]+)? \z/ or die(
52                         "Value \"$optval\" invalid for option limit",
53                         " (range expected)\n"
54                 );
55                 $start ||= 1;
56                 $start--;
57                 s/\A-0*\z// and $_ ||= undef for $end // ();
58
59                 $opt{hidemin} = sub {
60                         my ($lines) = @_;
61                         if ($start < 0) {
62                                 return max(0, $lines + $start + 2);
63                         }
64                         return $start;
65                 } if $start;
66                 $opt{hidemax} = sub {
67                         my ($limit, $offset) = @_;
68                         if ($end < 0) {
69                                 return $offset - $end - 1; # count
70                         }
71                         elsif ($start < 0) {
72                                 return $limit - $end + 1; # bottom
73                         }
74                         elsif ($end <= $limit) {
75                                 return $end - 1; # less
76                         }
77                         return $limit;
78                 } if defined $end;
79         },
80         'log|e!',
81         'header!',
82         'markers|m=s',
83         'graph-format=s' => sub {
84                 $opt{'graph-format'} = substr $_[1], 0, 1;
85         },
86         'spark|_!',
87         'indicators:s',
88         'palette=s' => sub {
89                 $opt{palette} = {
90                         ''     => [],
91                         fire   => [qw( 90 31 91 33 93 97 96 )],
92                         fire256=> [map {"38;5;$_"} qw(
93                                 235  52 88 124 160 196
94                                 202 208 214 220 226  227 228 229 230 231  159
95                         )],
96                         whites => [qw( 1;30 0;37 1;37 )],
97                         grays  => [map {"38;5;$_"} 0, 232..255, 15],
98                         random => [map {"38;5;$_"} List::Util::shuffle(17..231)],
99                         rainbow=> [map {"38;5;$_"}
100                                 196, # r
101                                 (map { 196 + $_*6   } 0..4), # +g
102                                 (map { 226 - $_*6*6 } 0..4), # -r
103                                 (map {  46 + $_     } 0..4), # +b
104                                 (map {  51 - $_*6   } 0..4), # -g
105                                 (map {  21 + $_*6*6 } 0..4), # +r
106                                 (map { 201 - $_     } 0..4), # -b
107                                 196,
108                         ],
109                 }->{$_[1]} // do {
110                         my @vals = split /[^0-9;]/, $_[1]
111                                 or die "Empty palette resulting from \"$_[1]\"\n";
112                         \@vals;
113                 };
114         },
115         'stat|s!',
116         'report=s',
117         'signal-stat=s',
118         'unmodified|u!',
119         'width|w=i',
120         'version|V' => sub {
121                 my $mascot = $opt{ascii} ? '=^,^=' : 'ฅ^•ﻌ•^ฅ';
122                 say "barcat $mascot version $VERSION";
123                 exit;
124         },
125         'usage|h' => sub {
126                 /^=/ ? last : print for readline *DATA;  # text between __END__ and pod
127                 exit;
128         },
129         'help|?'  => sub {
130                 require Pod::Usage;
131                 Pod::Usage::pod2usage(
132                         -exitval => 0, -perldocopt => '-oman', -verbose => 2,
133                 );
134         },
135 ) or exit 64;  # EX_USAGE
136 }
137
138 $opt{width} ||= $ENV{COLUMNS} || qx(tput cols) || 80 unless $opt{spark};
139 $opt{color} //= $ENV{NO_COLOR} ? 0 : -t *STDOUT;  # enable on tty
140 $opt{'graph-format'} //= '-';
141 $opt{trim}   *= $opt{width} / 100 if $opt{trimpct};
142 $opt{units}   = [split //, ' kMGTPEZYRQqryzafpn'.($opt{ascii} ? 'u' : 'μ').'m']
143         if $opt{'human-readable'};
144 $opt{anchor} //= qr/\A/;
145 $opt{'value-length'} = 4 if $opt{units};
146 $opt{'value-length'} = 1 if $opt{unmodified};
147 $opt{'signal-stat'} //= exists $SIG{INFO} ? 'INFO' : 'QUIT';
148 $opt{markers} //= '=avg >31.73v <68.27v +50v |0';
149 $opt{report} //= join('',
150         '${partsum+; $_ .= " of "}',
151         '${sum+; color(1); $_ .= " total in "}',
152         '${count#} values',
153         '${lines#; $_ = $_ != @order && " over $_ lines"}',
154         sprintf('${count: (%s)}', join ', ',
155                 '${min; color(31)} min',
156                 '${avg; $opt{reformat} or $_ = sprintf "%0.2f", $_; color(36)} avg',
157                 '${max; color(32)} max',
158         ),
159 );
160 $opt{palette} //= $opt{color} && [31, 90, 32];
161 $opt{indicators} = [split //, $opt{indicators} ||
162         ($opt{ascii} ? ' .oO' : $opt{spark} ? ' ▁▂▃▄▅▆▇█' : ' ▏▎▍▌▋▊▉█')
163 ] if defined $opt{indicators} or $opt{spark};
164 $opt{input} = (@ARGV && $ARGV[0] =~ m/\A[-0-9]/) ? \@ARGV : undef
165         and undef $opt{interval};
166
167 $opt{'calc-format'} = sub { sprintf '%*.*f', 0, 2, $_[0] };
168 $opt{'value-format'} = $opt{sexagesimal} ? sub {
169         my $s = abs($_[0]) + .5;
170         sprintf('%s%d:%02d:%02d', $_[0] < 0 && '-', $s/3600, $s/60%60, $s%60);
171 } : $opt{units} && sub {
172         my $unit = (
173                 log(abs $_[0] || 1) / log(10)
174                 - 3 * (abs($_[0]) < .9995)   # shift to smaller unit if below 1
175                 + 1e-15  # float imprecision
176         );
177         my $decimal = ($unit % 3) == ($unit < 0);
178         $unit -= log($decimal ? .995 : .9995) / log(10);  # rounded
179         $decimal = ($unit % 3) == ($unit < 0);
180         $decimal &&= $_[0] !~ /^-?0*[0-9]{1,3}$/;  # integer 0..999
181         sprintf('%*.*f%1s',
182                 3 + ($_[0] < 0), # digits plus optional negative sign
183                 $decimal,  # tenths
184                 $_[0] / 1000 ** int($unit/3),  # number
185                 $#{$opt{units}} * 1.5 < abs $unit ? sprintf('e%d', $unit) :
186                         $opt{units}->[$unit/3]  # suffix
187         );
188 } and $opt{reformat}++;
189 $opt{'value-format'} ||= sub { sprintf '%.8g', $_[0] };
190
191
192 my (@lines, @values, @order);
193
194 $SIG{$_} = \&show_stat for $opt{'signal-stat'} || ();
195 $SIG{ALRM} = sub {
196         show_lines();
197         alarm $opt{interval} if defined $opt{interval} and $opt{interval} > 0;
198 };
199 $SIG{INT} = \&show_exit;
200
201 if (defined $opt{interval}) {
202         $opt{interval} ||= 1;
203         alarm $opt{interval} if $opt{interval} > 0;
204
205         eval {
206                 require Tie::Array::Sorted;
207                 tie @order, 'Tie::Array::Sorted', sub { $_[1] <=> $_[0] };
208         } or warn $@, "Expect slowdown with large datasets!\n";
209 }
210
211 my $float = qr<[0-9]* [.]? [0-9]+ (?: e[+-]?[0-9]+ )?>; # positive numberish
212 my $valmatch = qr< $opt{anchor} ( \h* -? $float |) >x;
213 while (defined ($_ = $opt{input} ? shift @{ $opt{input} } : readline)) {
214         s/\r?\n\z//;
215         s/\A\h*// unless $opt{unmodified};
216         my $valnum = s/$valmatch/\n/ && $1;
217         push @values, $valnum;
218         push @order, $valnum if length $valnum;
219         if (defined $opt{trim} and defined $valnum) {
220                 my $trimpos = abs $opt{trim};
221                 $trimpos -= length $valnum if $opt{unmodified};
222                 if ($trimpos <= 1) {
223                         $_ = substr $_, 0, 2;
224                 }
225                 elsif (length > $trimpos) {
226                         # cut and replace (intentional lvalue for speed, contrary to PBP)
227                         substr($_, $trimpos - 1) = $opt{ascii} ? '>' : '…';
228                 }
229         }
230         push @lines, $_;
231         show_lines() if defined $opt{interval} and $opt{interval} < 0
232                 and $. % $opt{interval} == 0;
233 }
234
235 $SIG{INT} = 'DEFAULT';
236
237 sub color {
238         $opt{color} and defined $_[0] or return '';
239         return "\e[$_[0]m" if defined wantarray;
240         $_ = color(@_) . $_ . color(0) if defined;
241 }
242
243 sub show_lines {
244
245 state $nr = $opt{hidemin} ? $opt{hidemin}->($#lines) : 0;
246 @lines > $nr or return;
247
248 my $limit = $opt{hidemax} ? $opt{hidemax}->($#lines, $nr) : $#lines;
249
250 @order = sort { $b <=> $a } @order unless tied @order;
251 my $maxval = $opt{maxval} // (
252         $opt{hidemax} ? max grep { length } @values[$nr .. $limit] :
253         $order[0]
254 ) // 0;
255 my $minval = $opt{minval} // min $order[-1] // (), 0;
256 my $range = $maxval - $minval;
257 $range &&= log $range if $opt{log};
258 my $lenval = $opt{'value-length'} // max map { length } @order;
259 my $len    = defined $opt{trim} && $opt{trim} <= 0 ? -$opt{trim} + 1 :
260         max(map { length $values[$_] && length $lines[$_] } $nr .. $limit)
261         // 0;  # left padding
262 my $size   = defined $opt{width} && $range &&
263         ($opt{width} - $lenval - $len - !!$opt{indicators});  # bar multiplication
264
265 my @barmark;
266 if ($opt{markers} and $size > 0) {
267         for my $markspec (split /\h/, $opt{markers}) {
268                 my ($char, $func) = split //, $markspec, 2;
269                 my @pos = eval {
270                         if ($func eq 'avg') {
271                                 return sum(@order) / @order;
272                         }
273                         elsif ($func =~ /\A([0-9.]+)v\z/) {
274                                 $1 <= 100 or die(
275                                         "Invalid marker $char: percentile $1 out of bounds\n"
276                                 );
277                                 my $index = $#order * $1 / 100;
278                                 return ($order[$index] + $order[$index + .5]) / 2;
279                         }
280                         elsif ($func =~ /\A-?[0-9.]+\z/) {
281                                 return $func;
282                         }
283                         elsif ($func =~ /\A\/($float)\z/) {
284                                 my @range = my $multiple = my $next = $1;
285                                 while ($next < $maxval) {
286                                         $multiple *= 10 if $opt{log};
287                                         push @range, $next += $multiple;
288                                 }
289                                 return @range;
290                         }
291                         else {
292                                 die "Unknown marker $char: $func\n";
293                         }
294                 };
295                 @pos or do {
296                         warn $@ if $@;
297                         next;
298                 };
299                 for my $pos (@pos) {
300                         $pos -= $minval;
301                         $pos &&= log $pos if $opt{log};
302                         $pos >= 0 or next;
303                         color(36) for $barmark[$pos / $range * $size] = $char;
304                 }
305         }
306
307         state $lastmax = $maxval;
308         if ($maxval > $lastmax) {
309                 print ' ' x ($lenval + $len);
310                 print color(90);
311                 printf '%-*s',
312                         ($lastmax - $minval) * $size / $range + .5,
313                         '-' x (($values[$nr - 1] - $minval) * $size / $range);
314                 print color(92);
315                 say '+' x (($range - $lastmax) * $size / $range + .5);
316                 print color(0);
317                 $lastmax = $maxval;
318         }
319 }
320
321 say(
322         color(31), sprintf('%*s', $lenval, $minval),
323         color(90), '-', color(36), '+',
324         color(32), sprintf('%*s', $size - 3, $maxval),
325         color(90), '-', color(36), '+',
326         color(0),
327 ) if $opt{header};
328
329 while ($nr <= $limit) {
330         my $val = $values[$nr];
331         my $rel;
332         if (length $val) {
333                 $rel = $val - $minval;
334                 $rel &&= log $rel if $opt{log};
335                 $rel = min(1, $rel / $range) if $range; # 0..1
336         }
337         my $color = !length $val || !$opt{palette} ? undef :
338                 $val == $order[0] ? $opt{palette}->[-1] : # max
339                 $val == $order[-1] ? $opt{palette}->[0] : # min
340                 $opt{palette}->[ $rel * ($#{$opt{palette}} - 1) + 1 ];
341         my $indicator = $opt{indicators} && $opt{indicators}->[
342                 !length($val) || !$#{$opt{indicators}} ? 0 : # blank
343                 $#{$opt{indicators}} < 2 ? 1 :
344                 $val >= $order[0] ? -1 :
345                 $rel * ($#{$opt{indicators}} - 1e-14) + 1
346         ];
347
348         if ($opt{spark}) {
349                 say '' if $opt{width} and $nr and $nr % $opt{width} == 0;
350                 print color($color), $_ for $indicator;
351                 next;
352         }
353         print $indicator if defined $indicator;
354
355         if (length $val) {
356                 $val = sprintf("%*s", $lenval,
357                         $opt{reformat} ? $opt{'value-format'}->($val) : $val
358                 );
359                 color($color) for $val;
360         }
361         my $line = $lines[$nr] =~ s/\n/$val/r;
362         if (not length $val) {
363                 say $line;
364                 next;
365         }
366         printf '%-*s', $len + length($val), $line;
367         if ($rel and $size) {
368                 print $barmark[$_] // $opt{'graph-format'}
369                         for 1 .. $rel * $size + .5;
370         }
371         say '';
372 }
373 continue {
374         $nr++;
375 }
376 say $opt{palette} ? color(0) : '' if $opt{spark};
377
378         return $nr;
379 }
380
381 sub show_stat {
382         my %vars = (
383                 count => int @order,
384                 lines => int @lines,
385         );
386         my $linemin = !$opt{hidemin} ? 0 :
387                 ($vars{start} = $opt{hidemin}->($#lines));
388         my $linemax = !$opt{hidemax} ? $#lines :
389                 ($vars{end} = $opt{hidemax}->($#lines, $vars{start}));
390         if (@order) {
391                 $vars{partsum} = sum(0, grep {length} @values[$linemin .. $linemax])
392                         if $linemin <= $linemax and ($opt{hidemin} or $opt{hidemax});
393                 %vars = (%vars,
394                         sum => sum(@order),
395                         min => $order[-1],
396                         max => $order[0],
397                 );
398                 $vars{avg} = $vars{sum} / @order;
399         }
400         say varfmt($opt{report}, \%vars);
401         return 1;
402 }
403
404 sub varfmt {
405         my ($fmt, $vars) = @_;
406         $fmt =~ s[\$\{ \h*+ ((?: [^{}]++ | \{(?1)\} )+) \}]{
407                 my ($name, $op, $cmd) = split /\s*([;:])/, $1, 2;
408                 my $format = $name =~ s/\+// || $name !~ s/\#// && $opt{reformat};
409                 local $_ = $vars->{$name};
410                 defined && do {
411                         $_ = $opt{'value-format'}->($_) if $format;
412                         if ($cmd and $op eq ':') {
413                                 $_ = varfmt($cmd, $vars);
414                         }
415                         elsif ($cmd) {
416                                 eval $cmd;
417                                 warn "Error in \$$name report: $@" if $@;
418                         }
419                         $_;
420                 }
421         }eg;
422         return $fmt;
423 }
424
425 sub show_exit {
426         show_lines();
427         show_stat() if $opt{stat};
428         exit 130 if @_;  # 0x80+signo
429         exit;
430 }
431
432 show_exit();
433
434 __END__
435 Usage:                                               /\_/\
436   barcat [OPTIONS] [FILES|NUMBERS]                  (=•.•=)
437                                                     (u   u)
438 Options:
439   -a, --[no-]ascii         Restrict user interface to ASCII characters
440   -C, --[no-]color         Force colored output of values and bar markers
441   -f, --field=([+]N|REGEXP)
442                            Compare values after a given number of whitespace
443                            separators
444       --header             Prepend a chart axis with minimum and maximum
445                            values labeled
446   -H, --human-readable     Format values using SI unit prefixes
447       --sexagesimal        Convert seconds to HH:MM:SS time format
448   -t, --interval[=(N|-LINES)]
449                            Output partial progress every given number of
450                            seconds or input lines
451   -l, --length=[-]SIZE[%]  Trim line contents (between number and bars)
452   -L, --limit=[N|[-]START(-[END]|+N)]
453                            Select a range of lines to display
454   -e, --log                Logarithmic (exponential) scale instead of linear
455       --graph-format=CHAR  Glyph to repeat for the graph line
456   -m, --markers=FORMAT     Statistical positions to indicate on bars
457       --min=N, --max=N     Bars extend from 0 or the minimum value if lower
458       --palette=(PRESET|COLORS)
459                            Override colors of parsed numbers
460   -_, --spark              Replace lines by sparklines
461       --indicators[=CHARS] Prefix a unicode character corresponding to each
462                            value
463   -s, --stat               Total statistics after all data
464   -u, --unmodified         Do not reformat values, keeping leading whitespace
465       --value-length=SIZE  Reserved space for numbers
466   -w, --width=COLUMNS      Override the maximum number of columns to use
467   -h, --usage              Overview of available options
468       --help               Full pod documentation
469   -V, --version            Version information
470
471 =encoding utf8
472
473 =head1 NAME
474
475 barcat - concatenate texts with graph to visualize values
476
477 =head1 SYNOPSIS
478
479 B<barcat> [I<options>] [I<file>... | I<numbers>]
480
481 =head1 DESCRIPTION
482
483 Visualizes relative sizes of values read from input
484 (parameters, file(s) or STDIN).
485 Contents are concatenated similar to I<cat>,
486 but numbers are reformatted and a bar graph is appended to each line.
487
488 Don't worry, barcat does not drink and divide.
489 It can has various options for input and output (re)formatting,
490 but remains limited to one-dimensional charts.
491 For more complex graphing needs
492 you'll need a larger animal like I<gnuplot>.
493
494 =head1 OPTIONS
495
496 =over
497
498 =item B<-a>, B<-->[B<no->]B<ascii>
499
500 Restrict user interface to ASCII characters,
501 replacing default UTF-8 by their closest approximation.
502 Input is always interpreted as UTF-8 and shown as is.
503
504 =item B<-C>, B<-->[B<no->]B<color>
505
506 Force colored output of values and bar markers.
507 Defaults on if output is a tty,
508 disabled otherwise such as when piped or redirected.
509 Can also be disabled by setting B<-M>
510 or the I<NO_COLOR> environment variable.
511
512 =item B<-f>, B<--field>=([B<+>]I<number> | I<regexp>)
513
514 Compare values after a given number of whitespace separators,
515 or matching a regular expression.
516
517 Unspecified or B<-f0> means values are at the start of each line.
518 With B<-f1> the second word is taken instead.
519 A string can indicate the starting position of a value
520 (such as B<-f:> if preceded by colons),
521 or capture the numbers itself,
522 for example B<-f'(\d+)'> for the first digits anywhere.
523 A shorthand for this is C<+0>, or C<+N> to find the Nth number.
524
525 =item B<--header>
526
527 Prepend a chart axis with minimum and maximum values labeled.
528
529 =item B<-H>, B<--human-readable>
530
531 Format values using SI unit prefixes,
532 turning long numbers like C<12356789> into C<12.4M>.
533 Also changes an exponent C<1.602176634e-19> to C<160.2z>.
534 Short integers are aligned but kept without decimal point.
535
536 =item B<--sexagesimal>
537
538 Convert seconds to HH:MM:SS time format.
539
540 =item B<-t>, B<--interval>[=(I<seconds> | B<->I<lines>)]
541
542 Output partial progress every given number of seconds or input lines.
543 An update can also be forced by sending a I<SIGALRM> alarm signal.
544
545 =item B<-l>, B<--length>=[B<->]I<size>[B<%>]
546
547 Trim line contents (between number and bars)
548 to a maximum number of characters.
549 The exceeding part is replaced by an abbreviation sign,
550 unless B<--length=0>.
551
552 Prepend a dash (i.e. make negative) to enforce padding
553 regardless of encountered contents.
554
555 =item B<-L>, B<--limit>=[I<count> | [B<->]I<start>(B<->[I<end>] | B<+>I<count>)]
556
557 Select a range of lines to display.
558 A single integer indicates the last line number (like I<head>),
559 or first line counting from the bottom if negative (like I<tail>).
560
561 A range consists of a starting line number followed by either
562 a dash C<-> to an optional end, or plus sign C<+> with count.
563
564 All hidden input is still counted and analyzed for statistics,
565 but disregarded for padding and bar size.
566
567 =item B<-e>, B<--log>
568
569 Logarithmic (B<e>xponential) scale instead of linear
570 to compare orders of magnitude.
571
572 =item B<--graph-format>=I<character>
573
574 Glyph to repeat for the graph line.
575 Defaults to a dash C<->.
576
577 =item B<-m>, B<--markers>=I<format>
578
579 Statistical positions to indicate on bars.
580 A single indicator glyph precedes each position:
581
582 =over 2
583
584 =item I<number>
585
586 Exact value to match on the axis.
587 A vertical bar at the zero crossing is displayed by C<|0>
588 for negative values.
589 For example C<π3.14> would locate pi.
590
591 =item B</>I<interval>
592
593 Repeated at every multiple of a number.
594 For example C<:/1> for a grid at every integer.
595
596 =item I<percentage>B<v>
597
598 Ranked value at the given percentile.
599 The default shows C<+> at C<50v> for the mean or median;
600 the middle value or average between middle values.
601 One standard deviation right of the mean is at about C<68.3v>.
602 The default includes C<< >31.73v <68.27v >>
603 to encompass all I<normal> results, or 68% of all entries, by I<< <--> >>.
604
605 =item B<avg>
606
607 Matches the average;
608 the sum of all values divided by the number of counted lines.
609 Indicated by default as C<=>.
610
611 =back
612
613 =item B<--min>=I<number>, B<--max>=I<number>
614
615 Bars extend from 0 or the minimum value if lower,
616 to the largest value encountered.
617 These options can be set to customize this range.
618
619 =item B<--palette>=(I<preset> | I<color>...)
620
621 Override colors of parsed numbers.
622 Can be any CSI escape, such as C<90> for default dark gray,
623 or alternatively C<1;30> for bright black.
624
625 In case of additional colors,
626 the last is used for values equal to the maximum, the first for minima.
627 If unspecified, these are green and red respectively (C<31 90 32>).
628 Multiple intermediate colors will be distributed
629 relative to the size of values.
630
631 A non-numeric name can refer to a predefined color scheme:
632
633 =over 8
634
635 =item B<whites>
636
637 Minimal set of monochrome brightnesses.
638
639 =item B<grays>
640
641 Utilize the 24 grayscale ramp in 256-color terminals.
642
643 =item B<fire>
644
645 Gradient red to white in 7 out of 16 colors.
646
647 =item B<fire256>
648
649 Extended to 17 colors out of 256.
650
651 =item B<rainbow>
652
653 Saturated red to green to blue to red.
654
655 =item B<random>
656
657 All 215 extended colors in unrelated orders.
658
659 =back
660
661 =item B<-_>, B<--spark>
662
663 Replace lines by I<sparklines>,
664 single characters (configured by B<--indicators>)
665 corresponding to input values.
666
667 =item B<--indicators>[=I<characters>]
668
669 Prefix a unicode character corresponding to each value.
670 The first specified character will be used for non-values,
671 the remaining sequence will be distributed over the range of values.
672 Unspecified, block fill glyphs U+2581-2588 will be used.
673
674 =item B<-s>, B<--stat>
675
676 Total statistics after all data.
677
678 While processing (possibly a neverending pipe),
679 intermediate results are also shown on signal I<SIGINFO> if available (control+t on BSDs)
680 or I<SIGQUIT> otherwise (ctrl+\ on linux).
681
682 =item B<-u>, B<--unmodified>
683
684 Do not reformat values, keeping leading whitespace.
685 Keep original value alignment, which may be significant in some programs.
686
687 =item B<--value-length>=I<size>
688
689 Reserved space for numbers.
690
691 =item B<-w>, B<--width>=I<columns>
692
693 Override the maximum number of columns to use.
694 Appended graphics will extend to fill up the entire screen,
695 otherwise determined by the environment variable I<COLUMNS>
696 or by running the I<tput> command.
697
698 =item B<-h>, B<--usage>
699
700 Overview of available options.
701
702 =item B<--help>
703
704 Full pod documentation
705 as rendered by perldoc.
706
707 =item B<-V>, B<--version>
708
709 Version information.
710
711 =back
712
713 =head1 EXAMPLES
714
715 Draw a sine wave:
716
717     seq 30 | awk '{print sin($1/10)}' | barcat
718
719 Compare file sizes (with human-readable numbers):
720
721     du -d0 -b * | barcat -H
722
723 Same from formatted results, selecting the first numeric value:
724
725     tree -s --noreport | barcat -H -f+
726
727 Compare media metadata, like image size or play time:
728
729     exiftool -T -p '$megapixels ($imagesize) $filename' * | barcat
730
731     exiftool -T -p '$duration# $avgbitrate# $filename' * | barcat --sexagesimal
732
733     find -type f -print0 | xargs -0 -L1 \
734     ffprobe -show_format -of json -v error |
735     jq -r '.format|.duration+" "+.bit_rate+" "+.filename' | barcat --sex
736
737 Memory usage of user processes with long names truncated:
738
739     ps xo rss,pid,cmd | barcat -l40
740
741 Monitor network latency from prefixed results:
742
743     ping google.com | barcat -f'time=\K' -t
744
745 Commonly used after counting, for example users on the current server:
746
747     users | tr ' ' '\n' | sort | uniq -c | barcat
748
749 Letter frequencies in text files:
750
751     cat /usr/share/games/fortunes/*.u8 |
752     perl -CS -nE 'say for grep length, split /\PL*/, uc' |
753     sort | uniq -c | barcat
754
755 Number of HTTP requests per day:
756
757     cat httpd/access.log | cut -d\  -f4 | cut -d: -f1 | uniq -c | barcat
758
759 Any kind of database query results, preserving returned alignment:
760
761     echo 'SELECT sin(value * .1) FROM generate_series(0, 30) value' |
762     psql -t | barcat -u
763
764 In PostgreSQL from within the client; a fancy C<\dt+> perhaps:
765
766     > SELECT schemaname, relname, pg_total_relation_size(relid)
767       FROM pg_statio_user_tables ORDER BY idx_blks_hit
768       \g |barcat -uHf+
769
770 Same thing in SQLite (requires the sqlite3 client):
771
772     > .once |barcat -Hf+
773     > SELECT name, sum(pgsize) FROM dbstat GROUP BY 1;
774
775 Earthquakes worldwide magnitude 1+ in the last 24 hours:
776
777     curl https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/1.0_day.csv |
778     column -ts, -n | barcat -f4 -u -l80%
779
780 External datasets, like movies per year:
781
782     curl https://github.com/prust/wikipedia-movie-data/raw/master/movies.json -L |
783     jq .[].year | uniq -c | barcat
784
785 Pokémon height comparison:
786
787     curl https://github.com/Biuni/PokemonGO-Pokedex/raw/master/pokedex.json -L |
788     jq -r '.pokemon[] | [.height,.num,.name] | join(" ")' | barcat
789
790 USD/EUR exchange rate from CSV provided by the ECB:
791
792     curl https://sdw.ecb.europa.eu/export.do \
793          -Gd 'node=SEARCHRESULTS&q=EXR.D.USD.EUR.SP00.A&exportType=csv' |
794     barcat -f',\K' --value-length=7
795
796 Total population history in XML from the World Bank:
797
798     curl http://api.worldbank.org/v2/country/1W/indicator/SP.POP.TOTL |
799     xmlstarlet sel -t -m '*/*' -v wb:date -o ' ' -v wb:value -n |
800     barcat -f1 -H --markers=+/1e9
801
802 Population and other information for all countries:
803
804     curl http://download.geonames.org/export/dump/countryInfo.txt |
805     grep -v '^#\s' | column -ts$'\t' -n | barcat -f+2 -e -u -l150 -s
806
807 And of course various Git statistics, such commit count by year:
808
809     git log --pretty=%ci | cut -b-4 | uniq -c | barcat
810
811 Or the top 3 most frequent authors with statistics over all:
812
813     git shortlog -sn | barcat -L3 -s
814
815 Activity graph of the last days (substitute date C<-v-{}d> on BSD):
816
817     ( git log --pretty=%ci --since=30day | cut -b-10
818       seq 0 30 | xargs -i date +%F -d-{}day ) |
819     sort | uniq -c | awk '$1--' | barcat --spark
820
821 Sparkline graphics of simple input given as inline parameters:
822
823     barcat -_ 3 1 4 1 5 0 9 2 4
824
825 Misusing the spark functionality to draw a lolcat line:
826
827     seq $(tput cols) | barcat --spark --indicator=- --palette=rainbow
828
829 =head1 AUTHOR
830
831 Mischa POSLAWSKY <perl@shiar.org>
832
833 =head1 LICENSE
834
835 GPL3+.