0558da6bd0d569d42ef75328d43fb59a17b377b2
[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(' (%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                 print varfmt($opt{report}, \%vars);
400         }
401         say '';
402         return 1;
403 }
404
405 sub varfmt {
406         my ($fmt, $vars) = @_;
407         $fmt =~ s[\$\{ \h*+ ((?: [^{}]++ | \{(?1)\} )+) \}]{
408                 my ($name, $cmd) = split /\s*;/, $1, 2;
409                 my $format = $name =~ s/\+// || $name !~ s/\#// && $opt{reformat};
410                 local $_ = $vars->{$name};
411                 defined && do {
412                         $_ = $opt{'value-format'}->($_) if $format;
413                         if ($cmd) {
414                                 eval $cmd;
415                                 warn "Error in \$$name report: $@" if $@;
416                         }
417                         $_;
418                 }
419         }eg;
420         return $fmt;
421 }
422
423 sub show_exit {
424         show_lines();
425         show_stat() if $opt{stat};
426         exit 130 if @_;  # 0x80+signo
427         exit;
428 }
429
430 show_exit();
431
432 __END__
433 Usage:                                               /\_/\
434   barcat [OPTIONS] [FILES|NUMBERS]                  (=•.•=)
435                                                     (u   u)
436 Options:
437   -a, --[no-]ascii         Restrict user interface to ASCII characters
438   -C, --[no-]color         Force colored output of values and bar markers
439   -f, --field=([+]N|REGEXP)
440                            Compare values after a given number of whitespace
441                            separators
442       --header             Prepend a chart axis with minimum and maximum
443                            values labeled
444   -H, --human-readable     Format values using SI unit prefixes
445       --sexagesimal        Convert seconds to HH:MM:SS time format
446   -t, --interval[=(N|-LINES)]
447                            Output partial progress every given number of
448                            seconds or input lines
449   -l, --length=[-]SIZE[%]  Trim line contents (between number and bars)
450   -L, --limit=[N|[-]START(-[END]|+N)]
451                            Select a range of lines to display
452   -e, --log                Logarithmic (exponential) scale instead of linear
453       --graph-format=CHAR  Glyph to repeat for the graph line
454   -m, --markers=FORMAT     Statistical positions to indicate on bars
455       --min=N, --max=N     Bars extend from 0 or the minimum value if lower
456       --palette=(PRESET|COLORS)
457                            Override colors of parsed numbers
458   -_, --spark              Replace lines by sparklines
459       --indicators[=CHARS] Prefix a unicode character corresponding to each
460                            value
461   -s, --stat               Total statistics after all data
462   -u, --unmodified         Do not reformat values, keeping leading whitespace
463       --value-length=SIZE  Reserved space for numbers
464   -w, --width=COLUMNS      Override the maximum number of columns to use
465   -h, --usage              Overview of available options
466       --help               Full pod documentation
467   -V, --version            Version information
468
469 =encoding utf8
470
471 =head1 NAME
472
473 barcat - concatenate texts with graph to visualize values
474
475 =head1 SYNOPSIS
476
477 B<barcat> [<options>] [<file>... | <numbers>]
478
479 =head1 DESCRIPTION
480
481 Visualizes relative sizes of values read from input
482 (parameters, file(s) or STDIN).
483 Contents are concatenated similar to I<cat>,
484 but numbers are reformatted and a bar graph is appended to each line.
485
486 Don't worry, barcat does not drink and divide.
487 It can has various options for input and output (re)formatting,
488 but remains limited to one-dimensional charts.
489 For more complex graphing needs
490 you'll need a larger animal like I<gnuplot>.
491
492 =head1 OPTIONS
493
494 =over
495
496 =item -a, --[no-]ascii
497
498 Restrict user interface to ASCII characters,
499 replacing default UTF-8 by their closest approximation.
500 Input is always interpreted as UTF-8 and shown as is.
501
502 =item -C, --[no-]color
503
504 Force colored output of values and bar markers.
505 Defaults on if output is a tty,
506 disabled otherwise such as when piped or redirected.
507 Can also be disabled by setting I<-M>
508 or the I<NO_COLOR> environment variable.
509
510 =item -f, --field=([+]<number> | <regexp>)
511
512 Compare values after a given number of whitespace separators,
513 or matching a regular expression.
514
515 Unspecified or I<-f0> means values are at the start of each line.
516 With I<-f1> the second word is taken instead.
517 A string can indicate the starting position of a value
518 (such as I<-f:> if preceded by colons),
519 or capture the numbers itself,
520 for example I<-f'(\d+)'> for the first digits anywhere.
521 A shorthand for this is I<+0>, or I<+N> to find the Nth number.
522
523 =item --header
524
525 Prepend a chart axis with minimum and maximum values labeled.
526
527 =item -H, --human-readable
528
529 Format values using SI unit prefixes,
530 turning long numbers like I<12356789> into I<12.4M>.
531 Also changes an exponent I<1.602176634e-19> to I<160.2z>.
532 Short integers are aligned but kept without decimal point.
533
534 =item --sexagesimal
535
536 Convert seconds to HH:MM:SS time format.
537
538 =item -t, --interval[=(<seconds> | -<lines>)]
539
540 Output partial progress every given number of seconds or input lines.
541 An update can also be forced by sending a I<SIGALRM> alarm signal.
542
543 =item -l, --length=[-]<size>[%]
544
545 Trim line contents (between number and bars)
546 to a maximum number of characters.
547 The exceeding part is replaced by an abbreviation sign,
548 unless C<--length=0>.
549
550 Prepend a dash (i.e. make negative) to enforce padding
551 regardless of encountered contents.
552
553 =item -L, --limit=[<count> | [-]<start>(-[<end>] | +<count>)]
554
555 Select a range of lines to display.
556 A single integer indicates the last line number (like C<head>),
557 or first line counting from the bottom if negative (like C<tail>).
558
559 A range consists of a starting line number followed by either
560 a dash C<-> to an optional end, or plus sign C<+> with count.
561
562 All hidden input is still counted and analyzed for statistics,
563 but disregarded for padding and bar size.
564
565 =item -e, --log
566
567 Logarithmic (I<e>xponential) scale instead of linear
568 to compare orders of magnitude.
569
570 =item --graph-format=<character>
571
572 Glyph to repeat for the graph line.
573 Defaults to a dash C<->.
574
575 =item -m, --markers=<format>
576
577 Statistical positions to indicate on bars.
578 A single indicator glyph precedes each position:
579
580 =over 2
581
582 =item <number>
583
584 Exact value to match on the axis.
585 A vertical bar at the zero crossing is displayed by I<|0>
586 for negative values.
587 For example I<π3.14> would locate pi.
588
589 =item I</><interval>
590
591 Repeated at every multiple of a number.
592 For example I<:/1> for a grid at every integer.
593
594 =item <percentage>I<v>
595
596 Ranked value at the given percentile.
597 The default shows I<+> at I<50v> for the mean or median;
598 the middle value or average between middle values.
599 One standard deviation right of the mean is at about I<68.3v>.
600 The default includes I<< >31.73v <68.27v >>
601 to encompass all I<normal> results, or 68% of all entries, by B<< <--> >>.
602
603 =item I<avg>
604
605 Matches the average;
606 the sum of all values divided by the number of counted lines.
607 Indicated by default as I<=>.
608
609 =back
610
611 =item --min=<number>, --max=<number>
612
613 Bars extend from 0 or the minimum value if lower,
614 to the largest value encountered.
615 These options can be set to customize this range.
616
617 =item --palette=(<preset> | <color>...)
618
619 Override colors of parsed numbers.
620 Can be any CSI escape, such as I<90> for default dark grey,
621 or alternatively I<1;30> for bright black.
622
623 In case of additional colors,
624 the last is used for values equal to the maximum, the first for minima.
625 If unspecified, these are green and red respectively (I<31 90 32>).
626 Multiple intermediate colors will be distributed
627 relative to the size of values.
628
629 Predefined color schemes are named I<whites> and I<fire>,
630 or I<greys> and I<fire256> for 256-color variants.
631
632 =item -_, --spark
633
634 Replace lines by I<sparklines>,
635 single characters (configured by C<--indicators>)
636 corresponding to input values.
637
638 =item --indicators[=<characters>]
639
640 Prefix a unicode character corresponding to each value.
641 The first specified character will be used for non-values,
642 the remaining sequence will be distributed over the range of values.
643 Unspecified, block fill glyphs U+2581-2588 will be used.
644
645 =item -s, --stat
646
647 Total statistics after all data.
648
649 =item -u, --unmodified
650
651 Do not reformat values, keeping leading whitespace.
652 Keep original value alignment, which may be significant in some programs.
653
654 =item --value-length=<size>
655
656 Reserved space for numbers.
657
658 =item -w, --width=<columns>
659
660 Override the maximum number of columns to use.
661 Appended graphics will extend to fill up the entire screen,
662 otherwise determined by the environment variable I<COLUMNS>
663 or by running the C<tput> command.
664
665 =item -h, --usage
666
667 Overview of available options.
668
669 =item --help
670
671 Full pod documentation
672 as rendered by perldoc.
673
674 =item -V, --version
675
676 Version information.
677
678 =back
679
680 =head1 EXAMPLES
681
682 Draw a sine wave:
683
684     seq 30 | awk '{print sin($1/10)}' | barcat
685
686 Compare file sizes (with human-readable numbers):
687
688     du -d0 -b * | barcat -H
689
690 Same from formatted results, selecting the first numeric value:
691
692     tree -s --noreport | barcat -H -f+
693
694 Compare media metadata, like image size or play time:
695
696     exiftool -T -p '$megapixels ($imagesize) $filename' * | barcat
697
698     exiftool -T -p '$duration# $avgbitrate# $filename' * | barcat --sexagesimal
699
700     find -type f -print0 | xargs -0 -L1 \
701     ffprobe -show_format -of json -v error |
702     jq -r '.format|.duration+" "+.bit_rate+" "+.filename' | barcat --sex
703
704 Memory usage of user processes with long names truncated:
705
706     ps xo rss,pid,cmd | barcat -l40
707
708 Monitor network latency from prefixed results:
709
710     ping google.com | barcat -f'time=\K' -t
711
712 Commonly used after counting, for example users on the current server:
713
714     users | tr ' ' '\n' | sort | uniq -c | barcat
715
716 Letter frequencies in text files:
717
718     cat /usr/share/games/fortunes/*.u8 |
719     perl -CS -nE 'say for grep length, split /\PL*/, uc' |
720     sort | uniq -c | barcat
721
722 Number of HTTP requests per day:
723
724     cat httpd/access.log | cut -d\  -f4 | cut -d: -f1 | uniq -c | barcat
725
726 Any kind of database query results, preserving returned alignment:
727
728     echo 'SELECT sin(value * .1) FROM generate_series(0, 30) value' |
729     psql -t | barcat -u
730
731 In PostgreSQL from within the client; a fancy C<\dt+> perhaps:
732
733     > SELECT schemaname, relname, pg_total_relation_size(relid)
734       FROM pg_statio_user_tables ORDER BY idx_blks_hit
735       \g |barcat -uHf+
736
737 Same thing in SQLite (requires the sqlite3 client):
738
739     > .once |barcat -Hf+
740     > SELECT name, sum(pgsize) FROM dbstat GROUP BY 1;
741
742 Earthquakes worldwide magnitude 1+ in the last 24 hours:
743
744     curl https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/1.0_day.csv |
745     column -ts, -n | barcat -f4 -u -l80%
746
747 External datasets, like movies per year:
748
749     curl https://github.com/prust/wikipedia-movie-data/raw/master/movies.json -L |
750     jq .[].year | uniq -c | barcat
751
752 Pokémon height comparison:
753
754     curl https://github.com/Biuni/PokemonGO-Pokedex/raw/master/pokedex.json -L |
755     jq -r '.pokemon[] | [.height,.num,.name] | join(" ")' | barcat
756
757 USD/EUR exchange rate from CSV provided by the ECB:
758
759     curl https://sdw.ecb.europa.eu/export.do \
760          -Gd 'node=SEARCHRESULTS&q=EXR.D.USD.EUR.SP00.A&exportType=csv' |
761     barcat -f',\K' --value-length=7
762
763 Total population history in XML from the World Bank:
764
765     curl http://api.worldbank.org/v2/country/1W/indicator/SP.POP.TOTL |
766     xmlstarlet sel -t -m '*/*' -v wb:date -o ' ' -v wb:value -n |
767     barcat -f1 -H --markers=+/1e9
768
769 Population and other information for all countries:
770
771     curl http://download.geonames.org/export/dump/countryInfo.txt |
772     grep -v '^#\s' | column -ts$'\t' -n | barcat -f+2 -e -u -l150 -s
773
774 And of course various Git statistics, such commit count by year:
775
776     git log --pretty=%ci | cut -b-4 | uniq -c | barcat
777
778 Or the top 3 most frequent authors with statistics over all:
779
780     git shortlog -sn | barcat -L3 -s
781
782 Activity graph of the last days (substitute date C<-v-{}d> on BSD):
783
784     ( git log --pretty=%ci --since=30day | cut -b-10
785       seq 0 30 | xargs -i date +%F -d-{}day ) |
786     sort | uniq -c | awk '$1--' | barcat --spark
787
788 Sparkline graphics of simple input given as inline parameters:
789
790     barcat -_ 3 1 4 1 5 0 9 2 4
791
792 Misusing the spark functionality to draw a lolcat line:
793
794     seq $(tput cols) | barcat --spark --indicator=- --palette=rainbow
795
796 =head1 AUTHOR
797
798 Mischa POSLAWSKY <perl@shiar.org>
799
800 =head1 LICENSE
801
802 GPL3+.