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