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