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