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