distinguish column -n argument in examples
[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 otherwise determined by the environment variable I<COLUMNS>
590 or by running the C<tput> command.
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 -ts, -n | 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 -ts$'\t' -n | 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+.