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