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