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