sum calculation on demand and cached
[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                         min => $order[-1],
404                         max => $order[0],
405                 );
406         }
407         say varfmt($opt{report}, \%vars);
408         return 1;
409 }
410
411 sub calc {
412         my ($func) = @_;
413         if ($func eq 'avg') {
414                 return calc('sum') / @order;
415         }
416         elsif ($func eq 'sum') {
417                 state $cache;         # avoid recount
418                 state $cachednr = 0;  # if unchanged
419                 unless (@order == $cachednr) {
420                         $cache = sum(@order);
421                         $cachednr = @order;
422                 }
423                 return $cache;
424         }
425         elsif ($func =~ /\A([0-9.]+)v\z/) {
426                 $1 <= 100 or die(
427                         "percentile $1 out of bounds\n"
428                 );
429                 my $index = $#order * $1 / 100;
430                 my $f = $index - int $index;
431                 my $val = $order[$index];
432                 if ($f) {
433                         my $next = $order[$index + 1];
434                         $val -= $f * ($val - $next);
435                 }
436                 return $val;
437         }
438         elsif ($func =~ /\A-?[0-9.]+\z/) {
439                 return $func;
440         }
441         else {
442                 die "$func unknown\n";
443         }
444 }
445
446 sub varfmt {
447         my ($fmt, $vars) = @_;
448         $fmt =~ s[\$\{ \h*+ ((?: [^{}]++ | \{(?1)\} )+) \}]{
449                 my ($name, $op, $cmd) = split /\s*([;:])/, $1, 2;
450                 my $format = $name =~ s/\+// || $name !~ s/\#// && $opt{reformat};
451                 local $_ = exists $vars->{$name} ? $vars->{$name} : calc($name);
452                 defined && do {
453                         $_ = $opt{'value-format'}->($_) if $format;
454                         if ($cmd and $op eq ':') {
455                                 $_ = !!$_ && varfmt($cmd, $vars);
456                         }
457                         elsif ($cmd) {
458                                 eval $cmd;
459                                 warn "Error in \$$name report: $@" if $@;
460                         }
461                         $_;
462                 }
463         }eg;
464         return $fmt;
465 }
466
467 sub show_exit {
468         show_lines();
469         show_stat() if $opt{stat};
470         exit 130 if @_;  # 0x80+signo
471         exit;
472 }
473
474 show_exit();
475
476 __END__
477 Usage:                                               /\_/\
478   barcat [OPTIONS] [FILES|NUMBERS]                  (=•.•=)
479                                                     (u   u)
480 Options:
481   -a, --[no-]ascii         Restrict user interface to ASCII characters
482   -C, --[no-]color         Force colored output of values and bar markers
483   -c, --count              Omit repetitions and count the number of
484                            occurrences
485   -f, --field=([+]N|REGEXP)
486                            Compare values after a given number of whitespace
487                            separators
488       --header             Prepend a chart axis with minimum and maximum
489                            values labeled
490   -H, --human-readable     Format values using SI unit prefixes
491       --sexagesimal        Convert seconds to HH:MM:SS time format
492   -t, --interval[=(N|-LINES)]
493                            Output partial progress every given number of
494                            seconds or input lines
495   -l, --length=[-]SIZE[%]  Trim line contents (between number and bars)
496   -L, --limit=[N|[-]START(-[END]|+N)]
497                            Select a range of lines to display
498   -e, --log                Logarithmic (exponential) scale instead of linear
499       --graph-format=CHAR  Glyph to repeat for the graph line
500   -m, --markers=FORMAT     Statistical positions to indicate on bars
501       --min=N, --max=N     Bars extend from 0 or the minimum value if lower
502       --palette=(PRESET|COLORS)
503                            Override colors of parsed numbers
504   -_, --spark              Replace lines by sparklines
505       --indicators[=CHARS] Prefix a unicode character corresponding to each
506                            value
507   -s, --stat               Total statistics after all data
508   -u, --unmodified         Do not reformat values, keeping leading whitespace
509       --value-length=SIZE  Reserved space for numbers
510   -w, --width=COLUMNS      Override the maximum number of columns to use
511   -h, --usage              Overview of available options
512       --help               Full pod documentation
513   -V, --version            Version information
514
515 =encoding utf8
516
517 =head1 NAME
518
519 barcat - concatenate texts with graph to visualize values
520
521 =head1 SYNOPSIS
522
523 B<barcat> [I<options>] [I<file>... | I<numbers>]
524
525 =head1 DESCRIPTION
526
527 Visualizes relative sizes of values read from input
528 (parameters, file(s) or STDIN).
529 Contents are concatenated similar to I<cat>,
530 but numbers are reformatted and a bar graph is appended to each line.
531
532 It can has various options for input and output (re)formatting,
533 but remains limited to one-dimensional charts.
534 For more complex graphing needs
535 you'll need a larger animal like I<gnuplot>.
536
537 =head1 OPTIONS
538
539 =over
540
541 =item B<-a>, B<-->[B<no->]B<ascii>
542
543 Restrict user interface to ASCII characters,
544 replacing default UTF-8 by their closest approximation.
545 Input is always interpreted as UTF-8 and shown as is.
546
547 =item B<-C>, B<-->[B<no->]B<color>
548
549 Force colored output of values and bar markers.
550 Defaults on if output is a tty,
551 disabled otherwise such as when piped or redirected.
552 Can also be disabled by setting B<-M>
553 or the I<NO_COLOR> environment variable.
554
555 =item B<-c>, B<--count>
556
557 Omit repetitions and count the number of occurrences.
558 Similar to piping input through C<sort | uniq -c>
559 but keeping the order of first appearances.
560
561 Lines are omitted if they (or a specified field) are identical,
562 and the amount of matches is prepended and used as values
563 for bars and subsequent statistics.
564
565 =item B<-f>, B<--field>=([B<+>]I<number> | I<regexp>)
566
567 Compare values after a given number of whitespace separators,
568 or matching a regular expression.
569
570 Unspecified or B<-f0> means values are at the start of each line.
571 With B<-f1> the second word is taken instead.
572 A string can indicate the starting position of a value
573 (such as B<-f:> if preceded by colons),
574 or capture the numbers itself,
575 for example B<-f'(\d+)'> for the first digits anywhere.
576 A shorthand for this is C<+0>, or C<+N> to find the Nth number.
577
578 =item B<--header>
579
580 Prepend a chart axis with minimum and maximum values labeled.
581
582 =item B<-H>, B<--human-readable>
583
584 Format values using SI unit prefixes,
585 turning long numbers like C<12356789> into C<12.4M>.
586 Also changes an exponent C<1.602176634e-19> to C<160.2z>.
587 Short integers are aligned but kept without decimal point.
588
589 =item B<--sexagesimal>
590
591 Convert seconds to HH:MM:SS time format.
592
593 =item B<-t>, B<--interval>[=(I<seconds> | B<->I<lines>)]
594
595 Output partial progress every given number of seconds or input lines.
596 An update can also be forced by sending a I<SIGALRM> alarm signal.
597
598 =item B<-l>, B<--length>=[B<->]I<size>[B<%>]
599
600 Trim line contents (between number and bars)
601 to a maximum number of characters.
602 The exceeding part is replaced by an abbreviation sign,
603 unless B<--length=0>.
604
605 Prepend a dash (i.e. make negative) to enforce padding
606 regardless of encountered contents.
607
608 =item B<-L>, B<--limit>=[I<count> | [B<->]I<start>(B<->[I<end>] | B<+>I<count>)]
609
610 Select a range of lines to display.
611 A single integer indicates the last line number (like I<head>),
612 or first line counting from the bottom if negative (like I<tail>).
613
614 A range consists of a starting line number followed by either
615 a dash C<-> to an optional end, or plus sign C<+> with count.
616
617 All hidden input is still counted and analyzed for statistics,
618 but disregarded for padding and bar size.
619
620 =item B<-e>, B<--log>
621
622 Logarithmic (B<e>xponential) scale instead of linear
623 to compare orders of magnitude.
624
625 =item B<--graph-format>=I<character>
626
627 Glyph to repeat for the graph line.
628 Defaults to a dash C<->.
629
630 =item B<-m>, B<--markers>=I<format>
631
632 Statistical positions to indicate on bars.
633 A single indicator glyph precedes each position:
634
635 =over 2
636
637 =item I<number>
638
639 Exact value to match on the axis.
640 A vertical bar at the zero crossing is displayed by C<|0>
641 for negative values.
642 For example C<π3.14> would locate pi.
643
644 =item B</>I<interval>
645
646 Repeated at every multiple of a number.
647 For example C<:/1> for a grid at every integer.
648
649 =item I<percentage>B<v>
650
651 Ranked value at the given percentile,
652 or score at or below which a percentage falls
653 in its frequency distribution (inclusive).
654
655 The default shows C<+> at C<50v> for the mean or median:
656 the middle value or interpolation between two values.
657 One standard deviation below the median is at about C<68v>.
658 The default includes C<< >31.73v <68.27v >>
659 to encompass all I<normal> results, or 68% of all entries, by I<< <--> >>.
660
661 =item B<avg>
662
663 Matches the average (arithmetic mean);
664 the sum of all values divided by the number of counted lines.
665 Indicated by default as C<=>.
666
667 =back
668
669 =item B<--min>=I<number>, B<--max>=I<number>
670
671 Bars extend from 0 or the minimum value if lower,
672 to the largest value encountered.
673 These options can be set to customize this range.
674
675 =item B<--palette>=(I<preset> | I<color>...)
676
677 Override colors of parsed numbers.
678 Can be any CSI escape, such as C<90> for default dark gray,
679 or alternatively C<1;30> for bright black.
680
681 In case of additional colors,
682 the last is used for values equal to the maximum, the first for minima.
683 If unspecified, these are green and red respectively (C<31 90 32>).
684 Multiple intermediate colors will be distributed
685 relative to the size of values.
686
687 A non-numeric name can refer to a predefined color scheme:
688
689 =over 8
690
691 =item B<whites>
692
693 Minimal set of monochrome brightnesses.
694
695 =item B<grays>
696
697 Utilize the 24 grayscale ramp in 256-color terminals.
698
699 =item B<fire>
700
701 Gradient red to white in 7 out of 16 colors.
702
703 =item B<fire256>
704
705 Extended to 17 colors out of 256.
706
707 =item B<rainbow>
708
709 Saturated red to green to blue to red.
710
711 =item B<random>
712
713 All 215 extended colors in unrelated orders.
714
715 =back
716
717 =item B<-_>, B<--spark>
718
719 Replace lines by I<sparklines>,
720 single characters (configured by B<--indicators>)
721 corresponding to input values.
722
723 =item B<--indicators>[=I<characters>]
724
725 Prefix a unicode character corresponding to each value.
726 The first specified character will be used for non-values,
727 the remaining sequence will be distributed over the range of values.
728 Unspecified, block fill glyphs U+2581-2588 will be used.
729
730 =item B<-s>, B<--stat>
731
732 Total statistics after all data.
733
734 While processing (possibly a neverending pipe),
735 intermediate results are also shown on signal I<SIGINFO> if available (control+t on BSDs)
736 or I<SIGQUIT> otherwise (ctrl+\ on linux).
737
738 =item B<-u>, B<--unmodified>
739
740 Do not reformat values, keeping leading whitespace.
741 Keep original value alignment, which may be significant in some programs.
742
743 =item B<--value-length>=I<size>
744
745 Reserved space for numbers.
746
747 =item B<-w>, B<--width>=I<columns>
748
749 Override the maximum number of columns to use.
750 Appended graphics will extend to fill up the entire screen,
751 otherwise determined by the environment variable I<COLUMNS>
752 or by running the I<tput> command.
753
754 =item B<-h>, B<--usage>
755
756 Overview of available options.
757
758 =item B<--help>
759
760 Full pod documentation
761 as rendered by perldoc.
762
763 =item B<-V>, B<--version>
764
765 Version information.
766
767 =back
768
769 =head1 EXAMPLES
770
771 Draw a sine wave:
772
773     seq 30 | awk '{print sin($1/10)}' | barcat
774
775 Compare file sizes (with human-readable numbers):
776
777     du -d0 -b * | barcat -H
778
779 Same from formatted results, selecting the first numeric value:
780
781     tree -s --noreport | barcat -H -f+
782
783 Compare media metadata, like image size or play time:
784
785     exiftool -T -p '$megapixels ($imagesize) $filename' * | barcat
786
787     exiftool -T -p '$duration# $avgbitrate# $filename' * | barcat --sexagesimal
788
789     find -type f -print0 | xargs -0 -L1 \
790     ffprobe -show_format -of json -v error |
791     jq -r '.format|.duration+" "+.bit_rate+" "+.filename' | barcat --sex
792
793 Memory usage of user processes with long names truncated:
794
795     ps xo rss,pid,cmd | barcat -l40
796
797 Monitor network latency from prefixed results:
798
799     ping google.com | barcat -f'time=\K' -t
800
801 Commonly used after counting, eg letter frequencies in text files:
802
803     cat /usr/share/games/fortunes/*.u8 |
804     perl -CS -nE 'say for grep length, split /\PL*/, uc' |
805     sort | uniq -c | barcat
806
807 Users on the current server while preserving order:
808
809     users | tr ' ' '\n' | barcat -c
810
811 Number of HTTP requests per day:
812
813     barcat -cf'\[([^:]+)' httpd/access.log
814
815 Any kind of database query results, preserving returned alignment:
816
817     echo 'SELECT sin(value * .1) FROM generate_series(0, 30) value' |
818     psql -t | barcat -u
819
820 In PostgreSQL from within the client; a fancy C<\dt+> perhaps:
821
822     > SELECT schemaname, relname, pg_total_relation_size(relid)
823       FROM pg_statio_user_tables ORDER BY idx_blks_hit
824       \g |barcat -uHf+
825
826 Same thing in SQLite (requires the sqlite3 client):
827
828     > .once |barcat -Hf+
829     > SELECT name, sum(pgsize) FROM dbstat GROUP BY 1;
830
831 Earthquakes worldwide magnitude 1+ in the last 24 hours:
832
833     curl https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/1.0_day.csv |
834     column -ts, -n | barcat -f4 -u -l80%
835
836 External datasets, like movies per year:
837
838     curl https://github.com/prust/wikipedia-movie-data/raw/master/movies.json -L |
839     jq .[].year | uniq -c | barcat
840
841 Pokémon height comparison:
842
843     curl https://github.com/Biuni/PokemonGO-Pokedex/raw/master/pokedex.json -L |
844     jq -r '.pokemon[] | [.height,.num,.name] | join(" ")' | barcat
845
846 USD/EUR exchange rate from CSV provided by the ECB:
847
848     curl https://sdw.ecb.europa.eu/export.do \
849          -Gd 'node=SEARCHRESULTS&q=EXR.D.USD.EUR.SP00.A&exportType=csv' |
850     barcat -f',\K' --value-length=7
851
852 Total population history in XML from the World Bank:
853
854     curl http://api.worldbank.org/v2/country/1W/indicator/SP.POP.TOTL |
855     xmlstarlet sel -t -m '*/*' -v wb:date -o ' ' -v wb:value -n |
856     barcat -f1 -H --markers=+/1e9
857
858 Population and other information for all countries:
859
860     curl http://download.geonames.org/export/dump/countryInfo.txt |
861     grep -v '^#\s' | column -ts$'\t' -n | barcat -f+2 -e -u -l150 -s
862
863 And of course various Git statistics, such commit count by year:
864
865     git log --pretty=%ci | cut -b-4 | uniq -c | barcat
866
867 Or the top 3 most frequent authors with statistics over all:
868
869     git shortlog -sn | barcat -L3 -s
870
871 Activity graph of the last days (substitute date C<-v-{}d> on BSD):
872
873     ( git log --pretty=%ci --since=30day | cut -b-10
874       seq 0 30 | xargs -i date +%F -d-{}day ) |
875     sort | uniq -c | awk '$1--' | barcat --spark
876
877 Sparkline graphics of simple input given as inline parameters:
878
879     barcat -_ 3 1 4 1 5 0 9 2 4
880
881 Misusing the spark functionality to draw a lolcat line:
882
883     seq $(tput cols) | barcat --spark --indicator=- --palette=rainbow
884
885 =head1 AUTHOR
886
887 Mischa POSLAWSKY <perl@shiar.org>
888
889 =head1 LICENSE
890
891 GPL3+.