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