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