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