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