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