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