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