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