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