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