version 1.09 before adding even more complex features
[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.09';
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                                 $1 <= 100 or die(
250                                         "Invalid marker $char: percentile $1 out of bounds\n"
251                                 );
252                                 my $index = $#order * $1 / 100;
253                                 return ($order[$index] + $order[$index + .5]) / 2;
254                         }
255                         elsif ($func =~ /\A-?[0-9.]+\z/) {
256                                 return $func;
257                         }
258                         else {
259                                 die "Unknown marker $char: $func\n";
260                         }
261                 };
262                 defined $pos or do {
263                         warn $@ if $@;
264                         next;
265                 };
266                 $pos -= $minval;
267                 $pos >= 0 or next;
268                 color(36) for $barmark[$pos * $size] = $char;
269         }
270
271         state $lastmax = $maxval;
272         if ($maxval > $lastmax) {
273                 print ' ' x ($lenval + $len);
274                 printf color(90);
275                 printf '%-*s',
276                         ($lastmax - $minval) * $size + .5,
277                         '-' x (($values[$nr - 1] - $minval) * $size);
278                 print color(92);
279                 say '+' x (($range - $lastmax) * $size + .5);
280                 print color(0);
281                 $lastmax = $maxval;
282         }
283 }
284
285 say(
286         color(31), sprintf('%*s', $lenval, $minval),
287         color(90), '-', color(36), '+',
288         color(32), sprintf('%*s', $size * $range - 3, $maxval),
289         color(90), '-', color(36), '+',
290         color(0),
291 ) if $opt{header};
292
293 while ($nr <= $limit) {
294         my $val = $values[$nr];
295         my $rel = length $val && $range && min(1, ($val - $minval) / $range);
296         my $color = !length $val || !$opt{palette} ? undef :
297                 $val == $order[0] ? $opt{palette}->[-1] : # max
298                 $val == $order[-1] ? $opt{palette}->[0] : # min
299                 $opt{palette}->[ $rel * ($#{$opt{palette}} - 1) + 1 ];
300         my $indicator = $opt{indicators} && $opt{indicators}->[
301                 !length($val) || !$#{$opt{indicators}} ? 0 : # blank
302                 $#{$opt{indicators}} < 2 ? 1 :
303                 $val >= $order[0] ? -1 :
304                 $rel * ($#{$opt{indicators}} - 1e-14) + 1
305         ];
306
307         if ($opt{spark}) {
308                 say '' if $opt{width} and $nr and $nr % $opt{width} == 0;
309                 print color($color), $_ for $indicator;
310                 next;
311         }
312         print $indicator if defined $indicator;
313
314         if (length $val) {
315                 $val = sprintf("%*s", $lenval,
316                         $opt{reformat} ? $opt{'value-format'}->($val) : $val
317                 );
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{'value-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{'value-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{reformat} ? $opt{'value-format'} : sub {$_[0]})->($order[-1]) . color(0),
357                         color(36) . ($opt{reformat} ? $opt{'value-format'} : $opt{'calc-format'})->($total / @order) . color(0),
358                         color(32) . ($opt{reformat} ? $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 otherwise determined by the environment variable I<COLUMNS>
592 or by running the C<tput> command.
593
594 =item -h, --usage
595
596 Overview of available options.
597
598 =item --help
599
600 Full pod documentation
601 as rendered by perldoc.
602
603 =item -V, --version
604
605 Version information.
606
607 =back
608
609 =head1 EXAMPLES
610
611 Draw a sine wave:
612
613     seq 30 | awk '{print sin($1/10)}' | barcat
614
615 Compare file sizes (with human-readable numbers):
616
617     du -d0 -b * | barcat -H
618
619 Same from formatted results, selecting the first numeric value:
620
621     tree -s --noreport | barcat -H -f+
622
623 Compare media metadata, like image size or play time:
624
625     exiftool -T -p '$megapixels ($imagesize) $filename' * | barcat
626
627     exiftool -T -p '$duration# $avgbitrate# $filename' * | barcat --sexagesimal
628
629     find -type f -print0 | xargs -0 -L1 \
630     ffprobe -show_format -of json -v error |
631     jq -r '.format|.duration+" "+.bit_rate+" "+.filename' | barcat --sex
632
633 Memory usage of user processes with long names truncated:
634
635     ps xo rss,pid,cmd | barcat -l40
636
637 Monitor network latency from prefixed results:
638
639     ping google.com | barcat -f'time=\K' -t
640
641 Commonly used after counting, for example users on the current server:
642
643     users | tr ' ' '\n' | sort | uniq -c | barcat
644
645 Letter frequencies in text files:
646
647     cat /usr/share/games/fortunes/*.u8 |
648     perl -CS -nE 'say for grep length, split /\PL*/, uc' |
649     sort | uniq -c | barcat
650
651 Number of HTTP requests per day:
652
653     cat httpd/access.log | cut -d\  -f4 | cut -d: -f1 | uniq -c | barcat
654
655 Any kind of database query results, preserving returned alignment:
656
657     echo 'SELECT sin(value * .1) FROM generate_series(0, 30) value' |
658     psql -t | barcat -u
659
660 In PostgreSQL from within the client; a fancy C<\dt+> perhaps:
661
662     > SELECT schemaname, relname, pg_total_relation_size(relid)
663       FROM pg_statio_user_tables ORDER BY idx_blks_hit
664       \g |barcat -uHf+
665
666 Same thing in SQLite (requires the sqlite3 client):
667
668     > .once |barcat -Hf+
669     > SELECT name, sum(pgsize) FROM dbstat GROUP BY 1;
670
671 Earthquakes worldwide magnitude 1+ in the last 24 hours:
672
673     curl https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/1.0_day.csv |
674     column -ts, -n | barcat -f4 -u -l80%
675
676 External datasets, like movies per year:
677
678     curl https://github.com/prust/wikipedia-movie-data/raw/master/movies.json -L |
679     jq .[].year | uniq -c | barcat
680
681 Pokémon height comparison:
682
683     curl https://github.com/Biuni/PokemonGO-Pokedex/raw/master/pokedex.json -L |
684     jq -r '.pokemon[] | [.height,.num,.name] | join(" ")' | barcat
685
686 USD/EUR exchange rate from CSV provided by the ECB:
687
688     curl https://sdw.ecb.europa.eu/export.do \
689          -Gd 'node=SEARCHRESULTS&q=EXR.D.USD.EUR.SP00.A&exportType=csv' |
690     barcat -f',\K' --value-length=7
691
692 Total population history in XML from the World Bank:
693
694     curl http://api.worldbank.org/v2/country/1W/indicator/SP.POP.TOTL |
695     xmlstarlet sel -t -m '*/*' -v wb:date -o ' ' -v wb:value -n |
696     barcat -f1 -H
697
698 Population and other information for all countries:
699
700     curl http://download.geonames.org/export/dump/countryInfo.txt |
701     grep -v '^#\s' | column -ts$'\t' -n | barcat -f+2 -u -l150 -s
702
703 And of course various Git statistics, such commit count by year:
704
705     git log --pretty=%ci | cut -b-4 | uniq -c | barcat
706
707 Or the top 3 most frequent authors with statistics over all:
708
709     git shortlog -sn | barcat -L3 -s
710
711 Activity graph of the last days (substitute date C<-v-{}d> on BSD):
712
713     ( git log --pretty=%ci --since=30day | cut -b-10
714       seq 0 30 | xargs -i date +%F -d-{}day ) |
715     sort | uniq -c | awk '$1--' | barcat --spark
716
717 Sparkline graphics of simple input given as inline parameters:
718
719     barcat -_ 3 1 4 1 5 0 9 2 4
720
721 Misusing the spark functionality to draw a lolcat line:
722
723     seq $(tput cols) | barcat --spark --indicator=- --palette=rainbow
724
725 =head1 AUTHOR
726
727 Mischa POSLAWSKY <perl@shiar.org>
728
729 =head1 LICENSE
730
731 GPL3+.