fabc9a5d5b8dacd10814d031e812ae78d06f23d5
[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> [<options>] [<file>... | <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 -a, --[no-]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 -C, --[no-]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 I<-M>
510 or the I<NO_COLOR> environment variable.
511
512 =item -f, --field=([+]<number> | <regexp>)
513
514 Compare values after a given number of whitespace separators,
515 or matching a regular expression.
516
517 Unspecified or I<-f0> means values are at the start of each line.
518 With I<-f1> the second word is taken instead.
519 A string can indicate the starting position of a value
520 (such as I<-f:> if preceded by colons),
521 or capture the numbers itself,
522 for example I<-f'(\d+)'> for the first digits anywhere.
523 A shorthand for this is I<+0>, or I<+N> to find the Nth number.
524
525 =item --header
526
527 Prepend a chart axis with minimum and maximum values labeled.
528
529 =item -H, --human-readable
530
531 Format values using SI unit prefixes,
532 turning long numbers like I<12356789> into I<12.4M>.
533 Also changes an exponent I<1.602176634e-19> to I<160.2z>.
534 Short integers are aligned but kept without decimal point.
535
536 =item --sexagesimal
537
538 Convert seconds to HH:MM:SS time format.
539
540 =item -t, --interval[=(<seconds> | -<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 -l, --length=[-]<size>[%]
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 C<--length=0>.
551
552 Prepend a dash (i.e. make negative) to enforce padding
553 regardless of encountered contents.
554
555 =item -L, --limit=[<count> | [-]<start>(-[<end>] | +<count>)]
556
557 Select a range of lines to display.
558 A single integer indicates the last line number (like C<head>),
559 or first line counting from the bottom if negative (like C<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 -e, --log
568
569 Logarithmic (I<e>xponential) scale instead of linear
570 to compare orders of magnitude.
571
572 =item --graph-format=<character>
573
574 Glyph to repeat for the graph line.
575 Defaults to a dash C<->.
576
577 =item -m, --markers=<format>
578
579 Statistical positions to indicate on bars.
580 A single indicator glyph precedes each position:
581
582 =over 2
583
584 =item <number>
585
586 Exact value to match on the axis.
587 A vertical bar at the zero crossing is displayed by I<|0>
588 for negative values.
589 For example I<π3.14> would locate pi.
590
591 =item I</><interval>
592
593 Repeated at every multiple of a number.
594 For example I<:/1> for a grid at every integer.
595
596 =item <percentage>I<v>
597
598 Ranked value at the given percentile.
599 The default shows I<+> at I<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 I<68.3v>.
602 The default includes I<< >31.73v <68.27v >>
603 to encompass all I<normal> results, or 68% of all entries, by B<< <--> >>.
604
605 =item I<avg>
606
607 Matches the average;
608 the sum of all values divided by the number of counted lines.
609 Indicated by default as I<=>.
610
611 =back
612
613 =item --min=<number>, --max=<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 --palette=(<preset> | <color>...)
620
621 Override colors of parsed numbers.
622 Can be any CSI escape, such as I<90> for default dark grey,
623 or alternatively I<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 (I<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 -_, --spark
635
636 Replace lines by I<sparklines>,
637 single characters (configured by C<--indicators>)
638 corresponding to input values.
639
640 =item --indicators[=<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 -s, --stat
648
649 Total statistics after all data.
650
651 =item -u, --unmodified
652
653 Do not reformat values, keeping leading whitespace.
654 Keep original value alignment, which may be significant in some programs.
655
656 =item --value-length=<size>
657
658 Reserved space for numbers.
659
660 =item -w, --width=<columns>
661
662 Override the maximum number of columns to use.
663 Appended graphics will extend to fill up the entire screen,
664 otherwise determined by the environment variable I<COLUMNS>
665 or by running the C<tput> command.
666
667 =item -h, --usage
668
669 Overview of available options.
670
671 =item --help
672
673 Full pod documentation
674 as rendered by perldoc.
675
676 =item -V, --version
677
678 Version information.
679
680 =back
681
682 =head1 EXAMPLES
683
684 Draw a sine wave:
685
686     seq 30 | awk '{print sin($1/10)}' | barcat
687
688 Compare file sizes (with human-readable numbers):
689
690     du -d0 -b * | barcat -H
691
692 Same from formatted results, selecting the first numeric value:
693
694     tree -s --noreport | barcat -H -f+
695
696 Compare media metadata, like image size or play time:
697
698     exiftool -T -p '$megapixels ($imagesize) $filename' * | barcat
699
700     exiftool -T -p '$duration# $avgbitrate# $filename' * | barcat --sexagesimal
701
702     find -type f -print0 | xargs -0 -L1 \
703     ffprobe -show_format -of json -v error |
704     jq -r '.format|.duration+" "+.bit_rate+" "+.filename' | barcat --sex
705
706 Memory usage of user processes with long names truncated:
707
708     ps xo rss,pid,cmd | barcat -l40
709
710 Monitor network latency from prefixed results:
711
712     ping google.com | barcat -f'time=\K' -t
713
714 Commonly used after counting, for example users on the current server:
715
716     users | tr ' ' '\n' | sort | uniq -c | barcat
717
718 Letter frequencies in text files:
719
720     cat /usr/share/games/fortunes/*.u8 |
721     perl -CS -nE 'say for grep length, split /\PL*/, uc' |
722     sort | uniq -c | barcat
723
724 Number of HTTP requests per day:
725
726     cat httpd/access.log | cut -d\  -f4 | cut -d: -f1 | uniq -c | barcat
727
728 Any kind of database query results, preserving returned alignment:
729
730     echo 'SELECT sin(value * .1) FROM generate_series(0, 30) value' |
731     psql -t | barcat -u
732
733 In PostgreSQL from within the client; a fancy C<\dt+> perhaps:
734
735     > SELECT schemaname, relname, pg_total_relation_size(relid)
736       FROM pg_statio_user_tables ORDER BY idx_blks_hit
737       \g |barcat -uHf+
738
739 Same thing in SQLite (requires the sqlite3 client):
740
741     > .once |barcat -Hf+
742     > SELECT name, sum(pgsize) FROM dbstat GROUP BY 1;
743
744 Earthquakes worldwide magnitude 1+ in the last 24 hours:
745
746     curl https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/1.0_day.csv |
747     column -ts, -n | barcat -f4 -u -l80%
748
749 External datasets, like movies per year:
750
751     curl https://github.com/prust/wikipedia-movie-data/raw/master/movies.json -L |
752     jq .[].year | uniq -c | barcat
753
754 Pokémon height comparison:
755
756     curl https://github.com/Biuni/PokemonGO-Pokedex/raw/master/pokedex.json -L |
757     jq -r '.pokemon[] | [.height,.num,.name] | join(" ")' | barcat
758
759 USD/EUR exchange rate from CSV provided by the ECB:
760
761     curl https://sdw.ecb.europa.eu/export.do \
762          -Gd 'node=SEARCHRESULTS&q=EXR.D.USD.EUR.SP00.A&exportType=csv' |
763     barcat -f',\K' --value-length=7
764
765 Total population history in XML from the World Bank:
766
767     curl http://api.worldbank.org/v2/country/1W/indicator/SP.POP.TOTL |
768     xmlstarlet sel -t -m '*/*' -v wb:date -o ' ' -v wb:value -n |
769     barcat -f1 -H --markers=+/1e9
770
771 Population and other information for all countries:
772
773     curl http://download.geonames.org/export/dump/countryInfo.txt |
774     grep -v '^#\s' | column -ts$'\t' -n | barcat -f+2 -e -u -l150 -s
775
776 And of course various Git statistics, such commit count by year:
777
778     git log --pretty=%ci | cut -b-4 | uniq -c | barcat
779
780 Or the top 3 most frequent authors with statistics over all:
781
782     git shortlog -sn | barcat -L3 -s
783
784 Activity graph of the last days (substitute date C<-v-{}d> on BSD):
785
786     ( git log --pretty=%ci --since=30day | cut -b-10
787       seq 0 30 | xargs -i date +%F -d-{}day ) |
788     sort | uniq -c | awk '$1--' | barcat --spark
789
790 Sparkline graphics of simple input given as inline parameters:
791
792     barcat -_ 3 1 4 1 5 0 9 2 4
793
794 Misusing the spark functionality to draw a lolcat line:
795
796     seq $(tput cols) | barcat --spark --indicator=- --palette=rainbow
797
798 =head1 AUTHOR
799
800 Mischa POSLAWSKY <perl@shiar.org>
801
802 =head1 LICENSE
803
804 GPL3+.